import { Vector3D, AssetBase, AbstractMethodError, RequestAnimationFrame, getTimer } from '@awayjs/core';
import { ShaderBase, IAnimator, AnimationNodeBase, IRenderContainer, _Render_RenderableBase, IElements } from '@awayjs/renderer';
import { AnimatorEvent } from '../events/AnimatorEvent';
import { IAnimationState } from './states/IAnimationState';
import { AnimationSetBase } from './AnimationSetBase';
/**
* Dispatched when playback of an animation inside the animator object starts.
*
* @eventType away3d.events.AnimatorEvent
*/
//[Event(name="start", type="away3d.events.AnimatorEvent")]
/**
* Dispatched when playback of an animation inside the animator object stops.
*
* @eventType away3d.events.AnimatorEvent
*/
//[Event(name="stop", type="away3d.events.AnimatorEvent")]
/**
* Dispatched when playback of an animation reaches the end of an animation.
*
* @eventType away3d.events.AnimatorEvent
*/
//[Event(name="cycle_complete", type="away3d.events.AnimatorEvent")]
/**
* Provides an abstract base class for animator classes that control animation output from a data set subtype of AnimationSetBase.
*
* @see away.animators.AnimationSetBase
*/
export class AnimatorBase extends AssetBase implements IAnimator {
public static assetType: string = '[asset Animator]';
private _broadcaster: RequestAnimationFrame;
private _isPlaying: boolean;
private _autoUpdate: boolean = true;
private _startEvent: AnimatorEvent;
private _stopEvent: AnimatorEvent;
private _cycleEvent: AnimatorEvent;
private _time: number = 0;
private _playbackSpeed: number = 1;
public _pAnimationSet: AnimationSetBase;
public _pOwners: Array = new Array();
public _pActiveNode: AnimationNodeBase;
public _pActiveState: IAnimationState;
public _pActiveAnimationName: string;
public _pAbsoluteTime: number = 0;
private _animationStates: Object = new Object();
/**
* Enables translation of the animated graphics from data returned per frame via the positionDelta property of the active animation node. Defaults to true.
*
* @see away.animators.IAnimationState#positionDelta
*/
public updatePosition: boolean = true;
public getAnimationState(node: AnimationNodeBase): IAnimationState {
const className: any = node.stateClass;
const uID: number = node.id;
if (this._animationStates[uID] == null)
this._animationStates[uID] = new className(this, node);
return this._animationStates[uID];
}
public getAnimationStateByName(name: string): IAnimationState {
return this.getAnimationState(this._pAnimationSet.getAnimation(name));
}
/**
* Returns the internal absolute time of the animator, calculated by the current time and the playback speed.
*
* @see #time
* @see #playbackSpeed
*/
public get absoluteTime(): number {
return this._pAbsoluteTime;
}
/**
* Returns the animation data set in use by the animator.
*/
public get animationSet(): AnimationSetBase {
return this._pAnimationSet;
}
/**
* Returns the current active animation state.
*/
public get activeState(): IAnimationState {
return this._pActiveState;
}
/**
* Returns the current active animation node.
*/
public get activeAnimation(): AnimationNodeBase {
return this._pAnimationSet.getAnimation(this._pActiveAnimationName);
}
/**
* Returns the current active animation node.
*/
public get activeAnimationName(): string {
return this._pActiveAnimationName;
}
/**
* Determines whether the animators internal update mechanisms are active. Used in cases
* where manual updates are required either via the time property or update() method.
* Defaults to true.
*
* @see #time
* @see #update()
*/
public get autoUpdate(): boolean {
return this._autoUpdate;
}
public set autoUpdate(value: boolean) {
if (this._autoUpdate == value)
return;
this._autoUpdate = value;
if (this._autoUpdate)
this.start(); else
this.stop();
}
/**
* Gets and sets the internal time clock of the animator.
*/
public get time(): number {
return this._time;
}
public set time(value: number) {
if (this._time == value)
return;
this.update(value);
}
/**
* Sets the animation phase of the current active state's animation clip(s).
*
* @param value The phase value to use. 0 represents the beginning of an animation clip, 1 represents the end.
*/
public phase(value: number): void {
this._pActiveState.phase(value);
}
/**
* Creates a new AnimatorBase object.
*
* @param animationSet The animation data set to be used by the animator object.
*/
constructor(animationSet: AnimationSetBase) {
super();
this._pAnimationSet = animationSet;
this._broadcaster = new RequestAnimationFrame(this.onEnterFrame, this);
}
/**
* The amount by which passed time should be scaled. Used to slow down or speed up animations. Defaults to 1.
*/
public get playbackSpeed(): number {
return this._playbackSpeed;
}
public set playbackSpeed(value: number) {
this._playbackSpeed = value;
}
public setRenderState(shader: ShaderBase, renderable: _Render_RenderableBase): void {
throw new AbstractMethodError();
}
/**
* Resumes the automatic playback clock controling the active state of the animator.
*/
public start(): void {
if (this._isPlaying || !this._autoUpdate)
return;
this._time = this._pAbsoluteTime = getTimer();
this._isPlaying = true;
this._broadcaster.start();
if (!this.hasEventListener(AnimatorEvent.START))
return;
if (this._startEvent == null)
this._startEvent = new AnimatorEvent(AnimatorEvent.START, this);
this.dispatchEvent(this._startEvent);
}
/**
* Pauses the automatic playback clock of the animator, in case manual updates are required via the
* time property or update() method.
*
* @see #time
* @see #update()
*/
public stop(): void {
if (!this._isPlaying)
return;
this._isPlaying = false;
this._broadcaster.stop();
if (!this.hasEventListener(AnimatorEvent.STOP))
return;
if (this._stopEvent == null)
this._stopEvent = new AnimatorEvent(AnimatorEvent.STOP, this);
this.dispatchEvent(this._stopEvent);
}
/**
* Provides a way to manually update the active state of the animator when automatic
* updates are disabled.
*
* @see #stop()
* @see #autoUpdate
*/
public update(time: number): void {
const dt: number = (time - this._time) * this.playbackSpeed;
this._pUpdateDeltaTime(dt);
this._time = time;
}
public reset(name: string, offset: number = 0): void {
this.getAnimationState(this._pAnimationSet.getAnimation(name)).offset(offset + this._pAbsoluteTime);
}
/**
* Used by the graphics object to which the animator is applied, registers the owner for internal use.
*
* @private
*/
public addOwner(entity: IRenderContainer): void {
this._pOwners.push(entity);
}
/**
* Used by the graphics object from which the animator is removed, unregisters the owner for internal use.
*
* @private
*/
public removeOwner(entity: IRenderContainer): void {
this._pOwners.splice(this._pOwners.indexOf(entity), 1);
}
/**
* Internal abstract method called when the time delta property of the animator's contents requires updating.
*
* @private
*/
public _pUpdateDeltaTime(dt: number): void {
this._pAbsoluteTime += dt;
this._pActiveState.update(this._pAbsoluteTime);
if (this.updatePosition)
this.applyPositionDelta();
}
/**
* Enter frame event handler for automatically updating the active state of the animator.
*/
private onEnterFrame(event: Event = null): void {
this.update(getTimer());
}
private applyPositionDelta(): void {
const delta: Vector3D = this._pActiveState.positionDelta;
const dist: number = delta.length;
let len: number;
if (dist > 0) {
len = this._pOwners.length;
for (let i: number = 0; i < len; ++i)
this._pOwners[i].transform.translateLocal(delta, dist);
}
}
/**
* for internal use.
*
* @private
*/
public dispatchCycleEvent(): void {
if (this.hasEventListener(AnimatorEvent.CYCLE_COMPLETE)) {
if (this._cycleEvent == null)
this._cycleEvent = new AnimatorEvent(AnimatorEvent.CYCLE_COMPLETE, this);
this.dispatchEvent(this._cycleEvent);
}
}
/**
* @inheritDoc
*/
public clone(): AnimatorBase {
throw new AbstractMethodError();
}
/**
* @inheritDoc
*/
public dispose(): void {
}
public invalidateElements(): void {
let entity: IRenderContainer;
const len: number = this._pOwners.length;
for (let i: number = 0; i < len; i++) {
entity = this._pOwners[i];
entity.invalidateElements();
}
}
/**
* @inheritDoc
*/
public testGPUCompatibility(shader: ShaderBase): void {
throw new AbstractMethodError();
}
/**
* @inheritDoc
*/
public get assetType(): string {
return AnimatorBase.assetType;
}
public getRenderableElements(renderable: _Render_RenderableBase, sourceElements: IElements): IElements {
//nothing to do here
return sourceElements;
}
}