import { CommandExecutor } from '@uino/base-thing';
import { Utils } from '../common/Utils'
import { BaseComponent } from './BaseComponent';
import { EventType, AnimationDirectionType, LoopType, PlayStateType } from '../const';
const __ = {
private: Symbol('private'),
}
let _animationNames = [];
/**
* @class ModelAnimationComponent
* The model animation component.
* @memberof THING
* @extends THING.BaseComponent
* @public
*/
class ModelAnimationComponent extends BaseComponent {
static cDummyAnimationInterface = {
'animations': [],
'animationNames': [],
'playAnimation': function () { return false; },
'playAnimationAsync': function () { return Promise.resolve(); },
'blendAnimation': function () { return false; },
'blendAnimationAsync': function () { return Promise.resolve(); },
'hasAnimation': function () { return false; },
'pauseAnimation': function () { return false; },
'pauseAllAnimations': function () { return false; },
'resumeAnimation': function () { return false; },
'resumeAllAnimations': function () { return false; },
'stopAnimation': function () { return false; },
'stopAllAnimations': function () { return false; },
'isAnimationPlaying': function () { return false; },
'getAnimation': function () { return null; },
'getAnimationState': function () { return 'Stopped'; },
'getPlayingAnimations': function () { return []; },
'getAnimationDirectionType': function () { return 'Normal'; },
'setAnimationDirectionType': function () { return false; },
'getAnimationSpeed': function () { return 1; },
'setAnimationSpeed': function () { return false; },
'runDelayedCommands': function () { },
};
/**
* The model animation player of entity object.
*/
constructor() {
super();
this[__.private] = {};
let _private = this[__.private];
_private.commandExecutor = null;
_private.animations = new Map();
_private.animationPlayer = null;
}
// #region Private Functions
_initEvents() {
let _private = this[__.private];
let animationPlayer = _private.animationPlayer;
if (!animationPlayer) {
return;
}
// When play animation finished
animationPlayer.addEventListener('finished', (ev) => {
let name = ev.name;
let animation = _private.animations.get(name);
if (!animation) {
return;
}
animation.state = PlayStateType.Finished;
if (animation.onComplete) {
animation.onComplete(ev);
}
});
// When loop once finished
animationPlayer.addEventListener('loop', (ev) => {
});
}
_collectAnimiations() {
let _private = this[__.private];
let animationPlayer = _private.animationPlayer;
if (!animationPlayer) {
return;
}
_animationNames.length = 0;
animationPlayer.getNames(_animationNames);
_animationNames.forEach(name => {
let duration = animationPlayer.getAttribute(name, 'Duration');
// Pay attention: duration could be 0, but it's correct, just means it would play very fast !
_private.animations.set(name, {
name,
state: PlayStateType.Ready,
duration,
directionType: AnimationDirectionType.Normal,
speed: 1.0,
});
});
}
_playAnimation(param) {
this._init();
let _private = this[__.private];
// Get the playing parameters
let name = param['name'];
let loopType = param['loopType'];
let times = param['times'];
let reverse = param['reverse'];
let speed = Utils.parseValue(param['speed'], 1);
let frames = param['frames'] || null;
let onComplete = Utils.parseValue(param['onComplete'], param['complete']);
// Get animation info
let animation = _private.animations.get(name);
if (!animation) {
return;
}
// Stop animation playing
this.stopAnimation(name);
// Update animation info
animation.state = PlayStateType.Playing;
animation.speed = speed;
animation.onComplete = onComplete;
// Check whether it's reversed play mode
if (reverse) {
speed *= -1;
}
// Update player attributes
let animationPlayer = _private.animationPlayer;
animationPlayer.setAttribute(name, 'TimeScale', speed);
// Fix loop times
if (!times) {
if (loopType == LoopType.Repeat || loopType == LoopType.PingPong) {
times = Infinity;
}
else {
times = 1;
}
}
// Fix loop type
if (!loopType) {
loopType = LoopType.Repeat;
}
// Setup play options
let options = {
loopType,
loopTimes: times
};
// Set frames range
let start = 0.0, end = 1.0;
if (frames) {
if (Utils.isArray(frames)) {
start = frames[0];
end = frames.length > 1 ? frames[1] : 1.0;
}
else if (Utils.isNumber(frames)) {
start = frames;
}
else {
Utils.error(`The animation frames '${frames}' is invalid when play '${name}' animation`);
return;
}
// Set the start and end frames
options['framesRange'] = [start, end];
}
// Start to play animation
animationPlayer.play(name, options);
// Bind style with animation
let object = this.object;
object.body.style.bindAnimationEvents(object);
// Notify animation had played
object.trigger(EventType.PlayAnimation, { name });
}
_pauseAnimation(name, paused) {
let _private = this[__.private];
// Get animation info
let animation = _private.animations.get(name);
if (!animation) {
return;
}
// Update animation player state
let animationPlayer = _private.animationPlayer;
if (paused) {
animation.state = PlayStateType.Paused;
animationPlayer.pause(name);
}
else {
animation.state = PlayStateType.Playing;
animationPlayer.resume(name);
}
}
_stopAnimation(name) {
let _private = this[__.private];
// Get animation info
let animation = _private.animations.get(name);
if (!animation) {
return;
}
let state = animation.state;
if (state == PlayStateType.Ready || state == PlayStateType.Stopped) {
return;
}
// Update animation info state
animation.state = PlayStateType.Stopped;
animation.onComplete = null;
// Update animation player state
_private.animationPlayer.stop(name);
// Notify animation had stopped
this.object.trigger(EventType.StopAnimation, { name });
}
_checkAnimationState(name, state) {
let _private = this[__.private];
if (!_private.animationPlayer) {
return false;
}
if (name) {
let animationState = _private.animationPlayer.getState(name);
if (animationState == state) {
return true;
}
}
else {
for (let [key, animation] of _private.animations) {
if (animation.state == state) {
return true;
}
}
}
return false;
}
_addDelayedCommand(funcName, args) {
let _private = this[__.private];
_private.commandExecutor = _private.commandExecutor || new CommandExecutor();
_private.commandExecutor.addCommand(funcName, args);
}
_init() {
let _private = this[__.private];
if (_private.animationPlayer) {
return;
}
_private.animationPlayer = this.object.bodyNode.getAttribute('AnimationPlayer');
this._initEvents();
this._collectAnimiations();
}
// #endregion
// #region BaseComponent Interface
onRemove() {
let _private = this[__.private];
if (_private.animationPlayer) {
_private.animationPlayer.dispose();
_private.animationPlayer = null;
}
super.onRemove();
}
onUnloadResource() {
let _private = this[__.private];
this.stopAllAnimations();
if (_private.animationPlayer) {
_private.animationPlayer.dispose();
_private.animationPlayer = null;
}
_private.animations.clear();
}
// #endregion
hasInited() {
let _private = this[__.private];
if (_private.animationPlayer) {
return true;
}
return false;
}
/**
* Check whether has animation by name.
* @param {String} name The animation name
* @returns {Boolean}
* @example
* var UinoSpaceman = new THING.Entity({url:'.assets/models/UinoSpaceman/UinoSpaceman.gltf'});
* UinoSpaceman.waitForComplete().then(() => {
* let ret = UinoSpaceman.hasAnimation('Walk');
* // @expect(ret == true);
* });
* @public
*/
hasAnimation(name) {
return this.animationNames.indexOf(name) !== -1;
}
pauseAnimation(name) {
if (this.object.loaded) {
// Process animations by name lists
if (Utils.isArray(name)) {
name.forEach(animationName => {
this._pauseAnimation(animationName, true);
});
}
// Process one animation
else if (Utils.isString(name)) {
this._pauseAnimation(name, true);
}
}
else {
this._addDelayedCommand('pauseAnimation', arguments);
}
}
pauseAllAnimations() {
let _private = this[__.private];
_private.animations.forEach(animation => {
if (animation.state == PlayStateType.Paused) {
return;
}
this._pauseAnimation(animation.name, true);
});
}
resumeAnimation(name) {
if (this.object.loaded) {
// Process animations by name lists
if (Utils.isArray(name)) {
name.forEach(animationName => {
this._pauseAnimation(animationName, false);
});
}
// Process one animation
else if (Utils.isString(name)) {
this._pauseAnimation(name, false);
}
}
else {
this._addDelayedCommand('resumeAnimation', arguments);
}
}
resumeAllAnimations() {
let _private = this[__.private];
_private.animations.forEach(animation => {
if (animation.state != PlayStateType.Paused) {
return;
}
this._pauseAnimation(animation.name, false);
});
}
/**
* Stop animation.
* @param {String} name The animation name.
* @example
* var UinoSpaceman = new THING.Entity({url:'.assets/models/UinoSpaceman/UinoSpaceman.gltf'});
* UinoSpaceman.waitForComplete().then(() => {
* UinoSpaceman.playAnimation({ name: 'Walk', loopType: "Repeat" });
* let ret1 = UinoSpaceman.isAnimationPlaying('Walk');
* UinoSpaceman.stopAnimation('Walk');
* let ret2 = UinoSpaceman.getAnimationState() == 'Stopped';
* // @expect(ret1 == true && ret2 == true);
* });
* @public
*/
stopAnimation(name) {
if (this.object.loaded) {
// Pause spcecified animations by name lists
if (Utils.isArray(name)) {
name.forEach(animationName => {
this._stopAnimation(animationName);
});
}
// Pause one animation
else if (Utils.isString(name)) {
this._stopAnimation(name);
}
}
else {
this._addDelayedCommand('stopAnimation', arguments);
}
}
/**
* Stop all animations.
* @example
* var UinoSpaceman = new THING.Entity({url:'.assets/models/UinoSpaceman/UinoSpaceman.gltf'});
* UinoSpaceman.waitForComplete().then(() => {
* UinoSpaceman.playAnimation({ name: 'Walk', loopType: "Repeat" });
* let ret1 = UinoSpaceman.isAnimationPlaying('Walk');
* UinoSpaceman.stopAllAnimations();
* let ret2 = UinoSpaceman.getAnimationState() == 'Stopped';
* // @expect(ret1 == true && ret2 == true);
* });
* @public
*/
stopAllAnimations() {
let _private = this[__.private];
_private.animations.forEach(animation => {
this._stopAnimation(animation.name);
});
}
playAnimation(param = {}) {
if (this.object.loaded) {
// Stop all animations
this.stopAllAnimations();
// Use only one animation name to play
if (Utils.isString(param)) {
param = { name: param };
}
// Start to play animation
this._playAnimation(param);
}
else {
this._addDelayedCommand('playAnimation', arguments);
}
}
playAnimationAsync(param = {}) {
return new Promise((resolve, reject) => {
let onComplete = Utils.parseValue(param['onComplete'], param['complete']);
param['onComplete'] = function (ev) {
if (onComplete) {
onComplete(ev);
}
resolve();
};
this.playAnimation(param);
});
}
blendAnimation(param = {}) {
if (this.object.loaded) {
// Use only one animation name to play
if (Utils.isString(param)) {
param = { name: param };
}
// Start to play animation
this._playAnimation(param);
}
else {
this._addDelayedCommand('blendAnimation', arguments);
}
}
blendAnimationAsync(param = {}) {
return new Promise((resolve, reject) => {
let onComplete = Utils.parseValue(param['onComplete'], param['complete']);
param['onComplete'] = function (ev) {
if (onComplete) {
onComplete(ev);
}
resolve();
};
this.blendAnimation(param);
});
}
isAnimationPlaying(name) {
return this._checkAnimationState(name, PlayStateType.Playing);
}
/**
* Check whether all animations are ready.
* @returns {Boolean}
* @private
*/
isAllAnimationsReady() {
let _private = this[__.private];
if (_private.animationPlayer) {
for (let [key, animation] of _private.animations) {
if (animation.state != PlayStateType.Ready && animation.state != PlayStateType.Stopped) {
return false;
}
}
}
return true;
}
getAnimation(name) {
this._init();
let _private = this[__.private];
return _private.animations.get(name);
}
getAnimationState(name) {
this._init();
let _private = this[__.private];
let animation = _private.animations.get(name);
if (!animation) {
return PlayStateType.Stopped;
}
return animation.state;
}
getPlayingAnimations() {
this._init();
let _private = this[__.private];
let playingAnimations = [];
_private.animations.forEach(animation => {
if (animation.state == PlayStateType.Playing) {
playingAnimations.push(animation);
}
});
return playingAnimations;
}
getAnimationDirectionType(name) {
this._init();
let _private = this[__.private];
let animation = _private.animations.get(name);
if (!animation) {
return AnimationDirectionType.Normal;
}
return animation.directionType;
}
setAnimationDirectionType(name, value) {
if (this.object.loaded) {
this._init();
let _private = this[__.private];
let animation = _private.animations.get(name);
if (!animation) {
return false;
}
animation.directionType = value;
let speed = animation.speed;
if (value == AnimationDirectionType.Reverse) {
speed *= -1;
}
let animationPlayer = _private.animationPlayer;
animationPlayer.setAttribute(name, 'TimeScale', speed);
}
else {
this._addDelayedCommand('setAnimationDirectionType', arguments);
}
return true;
}
getAnimationSpeed(name) {
this._init();
let _private = this[__.private];
let animation = _private.animations.get(name);
if (!animation) {
return 0;
}
return animation.speed;
}
setAnimationSpeed(name, value) {
if (this.object.loaded) {
this._init();
let _private = this[__.private];
let animation = _private.animations.get(name);
if (!animation) {
return false;
}
animation.speed = value;
let animationPlayer = _private.animationPlayer;
animationPlayer.setAttribute(name, 'TimeScale', value);
}
else {
this._addDelayedCommand('setAnimationDirectionType', arguments);
}
return true;
}
runDelayedCommands() {
let _private = this[__.private];
if (_private.commandExecutor) {
_private.commandExecutor.runCommands(this);
_private.commandExecutor = null;
}
}
get animationPlayer() {
this._init();
let _private = this[__.private];
return _private.animationPlayer;
}
/**
* Get all animations info.
* @type {Array<AnimationResult>}
* @example
* var UinoSpaceman = new THING.Entity({url:'.assets/models/UinoSpaceman/UinoSpaceman.gltf'});
* UinoSpaceman.waitForComplete().then(() => {
* let animations = UinoSpaceman.animations;
* let ret = animations[0].name == 'Walk';
* // @expect(ret == true);
* });
* @public
*/
get animations() {
this._init();
let _private = this[__.private];
return [..._private.animations.values()];
}
/**
* Get the animation names.
* @type {Array<String>}
* @example
* var UinoSpaceman = new THING.Entity({url:'.assets/models/UinoSpaceman/UinoSpaceman.gltf'});
* UinoSpaceman.waitForComplete().then(() => {
* let animations = UinoSpaceman.animationNames;
* let ret = animations[0] == 'Walk';
* // @expect(ret == true);
* });
* @public
*/
get animationNames() {
this._init();
let _private = this[__.private];
return [..._private.animations.keys()];
}
}
ModelAnimationComponent.exportProperties = [
'animations',
'animationNames'
];
ModelAnimationComponent.exportFunctions = [
'playAnimation',
'playAnimationAsync',
'blendAnimation',
'blendAnimationAsync',
'hasAnimation',
'isAnimationPlaying',
'pauseAnimation',
'pauseAllAnimations',
'resumeAnimation',
'resumeAllAnimations',
'stopAnimation',
'stopAllAnimations',
'getAnimation',
'getAnimationState',
'getPlayingAnimations',
'getAnimationDirectionType',
'setAnimationDirectionType',
'getAnimationSpeed',
'setAnimationSpeed'
];
export { ModelAnimationComponent }