UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

479 lines (476 loc) 17.9 kB
import { sortPriority } from '../../../core/sort.js'; import { AnimClip } from '../evaluator/anim-clip.js'; import { ANIM_STATE_START, ANIM_INTERRUPTION_NONE, ANIM_STATE_END, ANIM_STATE_ANY, ANIM_NOT_EQUAL_TO, ANIM_EQUAL_TO, ANIM_LESS_THAN_EQUAL_TO, ANIM_GREATER_THAN_EQUAL_TO, ANIM_LESS_THAN, ANIM_GREATER_THAN, ANIM_INTERRUPTION_NEXT_PREV, ANIM_INTERRUPTION_PREV_NEXT, ANIM_INTERRUPTION_NEXT, ANIM_INTERRUPTION_PREV, ANIM_PARAMETER_TRIGGER, ANIM_CONTROL_STATES } from './constants.js'; import { AnimState } from './anim-state.js'; import { AnimNode } from './anim-node.js'; import { AnimTransition } from './anim-transition.js'; class AnimController { constructor(animEvaluator, states, transitions, activate, eventHandler, findParameter, consumeTrigger){ this._states = {}; this._stateNames = []; this._findTransitionsFromStateCache = {}; this._findTransitionsBetweenStatesCache = {}; this._previousStateName = null; this._activeStateName = ANIM_STATE_START; this._activeStateDuration = 0; this._activeStateDurationDirty = true; this._playing = false; this._currTransitionTime = 1; this._totalTransitionTime = 1; this._isTransitioning = false; this._transitionInterruptionSource = ANIM_INTERRUPTION_NONE; this._transitionPreviousStates = []; this._timeInState = 0; this._timeInStateBefore = 0; this.findParameter = (name)=>{ return this._findParameter(name); }; this._animEvaluator = animEvaluator; this._eventHandler = eventHandler; this._findParameter = findParameter; this._consumeTrigger = consumeTrigger; for(let i = 0; i < states.length; i++){ this._states[states[i].name] = new AnimState(this, states[i].name, states[i].speed, states[i].loop, states[i].blendTree); this._stateNames.push(states[i].name); } this._transitions = transitions.map((transition)=>{ return new AnimTransition({ ...transition }); }); this._activate = activate; } get animEvaluator() { return this._animEvaluator; } set activeState(stateName) { this._activeStateName = stateName; } get activeState() { return this._findState(this._activeStateName); } get activeStateName() { return this._activeStateName; } get activeStateAnimations() { return this.activeState.animations; } set previousState(stateName) { this._previousStateName = stateName; } get previousState() { return this._findState(this._previousStateName); } get previousStateName() { return this._previousStateName; } get playable() { let playable = true; for(let i = 0; i < this._stateNames.length; i++){ if (!this._states[this._stateNames[i]].playable) { playable = false; } } return playable; } set playing(value) { this._playing = value; } get playing() { return this._playing; } get activeStateProgress() { return this._getActiveStateProgressForTime(this._timeInState); } get activeStateDuration() { if (this._activeStateDurationDirty) { let maxDuration = 0.0; for(let i = 0; i < this.activeStateAnimations.length; i++){ const activeClip = this._animEvaluator.findClip(this.activeStateAnimations[i].name); if (activeClip) { maxDuration = Math.max(maxDuration, activeClip.track.duration); } } this._activeStateDuration = maxDuration; this._activeStateDurationDirty = false; } return this._activeStateDuration; } set activeStateCurrentTime(time) { this._timeInStateBefore = time; this._timeInState = time; for(let i = 0; i < this.activeStateAnimations.length; i++){ const clip = this.animEvaluator.findClip(this.activeStateAnimations[i].name); if (clip) { clip.time = time; } } } get activeStateCurrentTime() { return this._timeInState; } get transitioning() { return this._isTransitioning; } get transitionProgress() { return this._currTransitionTime / this._totalTransitionTime; } get states() { return this._stateNames; } assignMask(mask) { return this._animEvaluator.assignMask(mask); } _findState(stateName) { return this._states[stateName]; } _getActiveStateProgressForTime(time) { if (this.activeStateName === ANIM_STATE_START || this.activeStateName === ANIM_STATE_END || this.activeStateName === ANIM_STATE_ANY) { return 1.0; } const activeClip = this._animEvaluator.findClip(this.activeStateAnimations[0].name); if (activeClip) { return activeClip.progressForTime(time); } return null; } _findTransitionsFromState(stateName) { let transitions = this._findTransitionsFromStateCache[stateName]; if (!transitions) { transitions = this._transitions.filter((transition)=>{ return transition.from === stateName; }); sortPriority(transitions); this._findTransitionsFromStateCache[stateName] = transitions; } return transitions; } _findTransitionsBetweenStates(sourceStateName, destinationStateName) { let transitions = this._findTransitionsBetweenStatesCache[`${sourceStateName}->${destinationStateName}`]; if (!transitions) { transitions = this._transitions.filter((transition)=>{ return transition.from === sourceStateName && transition.to === destinationStateName; }); sortPriority(transitions); this._findTransitionsBetweenStatesCache[`${sourceStateName}->${destinationStateName}`] = transitions; } return transitions; } _transitionHasConditionsMet(transition) { const conditions = transition.conditions; for(let i = 0; i < conditions.length; i++){ const condition = conditions[i]; const parameter = this._findParameter(condition.parameterName); switch(condition.predicate){ case ANIM_GREATER_THAN: if (!(parameter.value > condition.value)) return false; break; case ANIM_LESS_THAN: if (!(parameter.value < condition.value)) return false; break; case ANIM_GREATER_THAN_EQUAL_TO: if (!(parameter.value >= condition.value)) return false; break; case ANIM_LESS_THAN_EQUAL_TO: if (!(parameter.value <= condition.value)) return false; break; case ANIM_EQUAL_TO: if (!(parameter.value === condition.value)) return false; break; case ANIM_NOT_EQUAL_TO: if (!(parameter.value !== condition.value)) return false; break; } } return true; } _findTransition(from, to) { let transitions = []; if (from && to) { transitions = transitions.concat(this._findTransitionsBetweenStates(from, to)); } else { if (!this._isTransitioning) { transitions = transitions.concat(this._findTransitionsFromState(this._activeStateName)); transitions = transitions.concat(this._findTransitionsFromState(ANIM_STATE_ANY)); } else { switch(this._transitionInterruptionSource){ case ANIM_INTERRUPTION_PREV: transitions = transitions.concat(this._findTransitionsFromState(this._previousStateName)); transitions = transitions.concat(this._findTransitionsFromState(ANIM_STATE_ANY)); break; case ANIM_INTERRUPTION_NEXT: transitions = transitions.concat(this._findTransitionsFromState(this._activeStateName)); transitions = transitions.concat(this._findTransitionsFromState(ANIM_STATE_ANY)); break; case ANIM_INTERRUPTION_PREV_NEXT: transitions = transitions.concat(this._findTransitionsFromState(this._previousStateName)); transitions = transitions.concat(this._findTransitionsFromState(this._activeStateName)); transitions = transitions.concat(this._findTransitionsFromState(ANIM_STATE_ANY)); break; case ANIM_INTERRUPTION_NEXT_PREV: transitions = transitions.concat(this._findTransitionsFromState(this._activeStateName)); transitions = transitions.concat(this._findTransitionsFromState(this._previousStateName)); transitions = transitions.concat(this._findTransitionsFromState(ANIM_STATE_ANY)); break; } } } transitions = transitions.filter((transition)=>{ if (transition.to === this.activeStateName) { return false; } if (transition.hasExitTime) { let progressBefore = this._getActiveStateProgressForTime(this._timeInStateBefore); let progress = this._getActiveStateProgressForTime(this._timeInState); if (transition.exitTime < 1.0 && this.activeState.loop) { progressBefore -= Math.floor(progressBefore); progress -= Math.floor(progress); } if (progress === progressBefore) { if (progress !== transition.exitTime) { return null; } } else if (!(transition.exitTime > progressBefore && transition.exitTime <= progress)) { return null; } } return this._transitionHasConditionsMet(transition); }); if (transitions.length > 0) { const transition = transitions[0]; if (transition.to === ANIM_STATE_END) { const startTransition = this._findTransitionsFromState(ANIM_STATE_START)[0]; transition.to = startTransition.to; } return transition; } return null; } updateStateFromTransition(transition) { let state; let animation; let clip; this.previousState = transition.from ? this.activeStateName : null; this.activeState = transition.to; this._activeStateDurationDirty = true; for(let i = 0; i < transition.conditions.length; i++){ const condition = transition.conditions[i]; const parameter = this._findParameter(condition.parameterName); if (parameter.type === ANIM_PARAMETER_TRIGGER) { this._consumeTrigger(condition.parameterName); } } if (this.previousState) { if (!this._isTransitioning) { this._transitionPreviousStates = []; } this._transitionPreviousStates.push({ name: this._previousStateName, weight: 1 }); const interpolatedTime = Math.min(this._totalTransitionTime !== 0 ? this._currTransitionTime / this._totalTransitionTime : 1, 1.0); for(let i = 0; i < this._transitionPreviousStates.length; i++){ if (!this._isTransitioning) { this._transitionPreviousStates[i].weight = 1.0; } else if (i !== this._transitionPreviousStates.length - 1) { this._transitionPreviousStates[i].weight *= 1.0 - interpolatedTime; } else { this._transitionPreviousStates[i].weight = interpolatedTime; } state = this._findState(this._transitionPreviousStates[i].name); for(let j = 0; j < state.animations.length; j++){ animation = state.animations[j]; clip = this._animEvaluator.findClip(`${animation.name}.previous.${i}`); if (!clip) { clip = this._animEvaluator.findClip(animation.name); clip.name = `${animation.name}.previous.${i}`; } if (i !== this._transitionPreviousStates.length - 1) { clip.pause(); } } } } this._isTransitioning = true; this._totalTransitionTime = transition.time; this._currTransitionTime = 0; this._transitionInterruptionSource = transition.interruptionSource; const activeState = this.activeState; const hasTransitionOffset = transition.transitionOffset && transition.transitionOffset > 0.0 && transition.transitionOffset < 1.0; let timeInState = 0; let timeInStateBefore = 0; if (hasTransitionOffset) { const offsetTime = activeState.timelineDuration * transition.transitionOffset; timeInState = offsetTime; timeInStateBefore = offsetTime; } this._timeInState = timeInState; this._timeInStateBefore = timeInStateBefore; for(let i = 0; i < activeState.animations.length; i++){ clip = this._animEvaluator.findClip(activeState.animations[i].name); if (!clip) { const speed = Number.isFinite(activeState.animations[i].speed) ? activeState.animations[i].speed : activeState.speed; clip = new AnimClip(activeState.animations[i].animTrack, this._timeInState, speed, true, activeState.loop, this._eventHandler); clip.name = activeState.animations[i].name; this._animEvaluator.addClip(clip); } else { clip.reset(); } if (transition.time > 0) { clip.blendWeight = 0.0; } else { clip.blendWeight = activeState.animations[i].normalizedWeight; } clip.play(); if (hasTransitionOffset) { clip.time = activeState.timelineDuration * transition.transitionOffset; } else { const startTime = activeState.speed >= 0 ? 0 : this.activeStateDuration; clip.time = startTime; } } } _transitionToState(newStateName) { if (!this._findState(newStateName)) { return; } let transition = this._findTransition(this._activeStateName, newStateName); if (!transition) { this._animEvaluator.removeClips(); transition = new AnimTransition({ from: null, to: newStateName }); } this.updateStateFromTransition(transition); } assignAnimation(pathString, animTrack, speed, loop) { const path = pathString.split('.'); let state = this._findState(path[0]); if (!state) { state = new AnimState(this, path[0], speed); this._states[path[0]] = state; this._stateNames.push(path[0]); } state.addAnimation(path, animTrack); this._animEvaluator.updateClipTrack(state.name, animTrack); if (speed !== undefined) { state.speed = speed; } if (loop !== undefined) { state.loop = loop; } if (!this._playing && this._activate && this.playable) { this.play(); } this._activeStateDurationDirty = true; } removeNodeAnimations(nodeName) { if (ANIM_CONTROL_STATES.indexOf(nodeName) !== -1) { return false; } const state = this._findState(nodeName); if (!state) { return false; } state.animations = []; return true; } play(stateName) { if (stateName) { this._transitionToState(stateName); } this._playing = true; } pause() { this._playing = false; } reset() { this._previousStateName = null; this._activeStateName = ANIM_STATE_START; this._playing = false; this._currTransitionTime = 1.0; this._totalTransitionTime = 1.0; this._isTransitioning = false; this._timeInState = 0; this._timeInStateBefore = 0; this._animEvaluator.removeClips(); } rebind() { this._animEvaluator.rebind(); } update(dt) { if (!this._playing) { return; } let state; let animation; let clip; if (this.activeState.loop || this._timeInState < this.activeStateDuration) { this._timeInStateBefore = this._timeInState; this._timeInState += dt * this.activeState.speed; if (!this.activeState.loop && this._timeInState > this.activeStateDuration) { this._timeInState = this.activeStateDuration; dt = this.activeStateDuration - this._timeInStateBefore; } } const transition = this._findTransition(this._activeStateName); if (transition) { this.updateStateFromTransition(transition); } if (this._isTransitioning) { this._currTransitionTime += dt; if (this._currTransitionTime <= this._totalTransitionTime) { const interpolatedTime = this._totalTransitionTime !== 0 ? this._currTransitionTime / this._totalTransitionTime : 1; for(let i = 0; i < this._transitionPreviousStates.length; i++){ state = this._findState(this._transitionPreviousStates[i].name); const stateWeight = this._transitionPreviousStates[i].weight; for(let j = 0; j < state.animations.length; j++){ animation = state.animations[j]; clip = this._animEvaluator.findClip(`${animation.name}.previous.${i}`); if (clip) { clip.blendWeight = (1.0 - interpolatedTime) * animation.normalizedWeight * stateWeight; } } } state = this.activeState; for(let i = 0; i < state.animations.length; i++){ animation = state.animations[i]; this._animEvaluator.findClip(animation.name).blendWeight = interpolatedTime * animation.normalizedWeight; } } else { this._isTransitioning = false; const activeClips = this.activeStateAnimations.length; const totalClips = this._animEvaluator.clips.length; for(let i = 0; i < totalClips - activeClips; i++){ this._animEvaluator.removeClip(0); } this._transitionPreviousStates = []; state = this.activeState; for(let i = 0; i < state.animations.length; i++){ animation = state.animations[i]; clip = this._animEvaluator.findClip(animation.name); if (clip) { clip.blendWeight = animation.normalizedWeight; } } } } else { if (this.activeState._blendTree.constructor !== AnimNode) { state = this.activeState; for(let i = 0; i < state.animations.length; i++){ animation = state.animations[i]; clip = this._animEvaluator.findClip(animation.name); if (clip) { clip.blendWeight = animation.normalizedWeight; if (animation.parent.syncAnimations) { clip.speed = animation.speed; } } } } } this._animEvaluator.update(dt, this.activeState.hasAnimations); } } export { AnimController };