Skip to content

Commit

Permalink
Fix animation skipping when updating with appendAnimation method and …
Browse files Browse the repository at this point in the history
…improve general performance
  • Loading branch information
mohammadbaghaei committed Jan 8, 2025
1 parent 2035fde commit e2dd816
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 21 deletions.
33 changes: 26 additions & 7 deletions packages/model-viewer/src/features/animation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ interface PlayAnimationOptions {
}

interface AppendAnimationOptions {
pingpong: boolean, repetitions: number, weight: number, timeScale: number,
fade: boolean|number, warp: boolean|number
pingpong: boolean, repetitions: number|null, weight: number,
timeScale: number, fade: boolean|number, warp: boolean|number,
relativeWarp: boolean
}

interface DetachAnimationOptions {
Expand All @@ -46,11 +47,12 @@ const DEFAULT_PLAY_OPTIONS: PlayAnimationOptions = {

const DEFAULT_APPEND_OPTIONS: AppendAnimationOptions = {
pingpong: false,
repetitions: Infinity,
repetitions: null,
weight: 1,
timeScale: 1,
fade: false,
warp: false
warp: false,
relativeWarp: true
};

const DEFAULT_DETACH_OPTIONS: DetachAnimationOptions = {
Expand Down Expand Up @@ -92,16 +94,29 @@ export const AnimationMixin = <T extends Constructor<ModelViewerElementBase>>(
const count = e.action._loopCount;
const name = e.action._clip.name;
const uuid = e.action._clip.uuid;
const targetAnimation =
this[$scene].markedAnimations.find(e => e.name === name);

if (targetAnimation) {
this[$scene].updateAnimationLoop(
targetAnimation.name,
targetAnimation.loopMode,
targetAnimation.repetitionCount);
const filtredArr =
this[$scene].markedAnimations.filter(e => e.name !== name);
this[$scene].markedAnimations = filtredArr;
}

this.dispatchEvent(
new CustomEvent('loop', {detail: {count, name, uuid}}));
});
this[$scene].subscribeMixerEvent('finished', (e) => {
if (!this[$scene].appendedAnimations.includes(e.action._clip.name)) {
this[$paused] = true;
} else {
const result = this[$scene].appendedAnimations.filter(
const filterdList = this[$scene].appendedAnimations.filter(
i => i !== e.action._clip.name);
this[$scene].appendedAnimations = result;
this[$scene].appendedAnimations = filterdList;
}
this.dispatchEvent(new CustomEvent('finished'));
});
Expand Down Expand Up @@ -252,14 +267,18 @@ export const AnimationMixin = <T extends Constructor<ModelViewerElementBase>>(
LoopPingPong :
(repetitions === 1 ? LoopOnce : LoopRepeat);

const needsToStop = !!options.repetitions;

this[$scene].appendAnimation(
animationName ? animationName : this.animationName,
mode,
repetitions,
options.weight,
options.timeScale,
options.fade,
options.warp);
options.warp,
options.relativeWarp,
needsToStop);

// If we are currently paused, we need to force a render so that
// the scene updates to the first frame of the new animation
Expand Down
75 changes: 63 additions & 12 deletions packages/model-viewer/src/three-components/ModelScene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ export interface ModelSceneConfig {
height: number;
}

export interface MarkedAnimation {
name: string, loopMode: AnimationActionLoopStyles, repetitionCount: number
}

export type IlluminationRole = 'primary'|'secondary';

export const IlluminationRole: {[index: string]: IlluminationRole} = {
Expand Down Expand Up @@ -77,6 +81,7 @@ export class ModelScene extends Scene {
public renderCount = 0;
public externalRenderer: RendererInterface|null = null;
public appendedAnimations: Array<string> = [];
public markedAnimations: Array<MarkedAnimation> = [];

// These default camera values are never used, as they are reset once the
// model is loaded and framing is computed.
Expand Down Expand Up @@ -783,7 +788,8 @@ export class ModelScene extends Scene {
name: string = '', loopMode: AnimationActionLoopStyles = LoopRepeat,
repetitionCount: number = Infinity, weight: number = 1,
timeScale: number = 1, fade: boolean|number = false,
warp: boolean|number = false) {
warp: boolean|number = false, relativeWarp: boolean = true,
needsToStop: boolean = false) {
if (this._currentGLTF == null || name === this.element.animationName) {
return;
}
Expand Down Expand Up @@ -812,6 +818,8 @@ export class ModelScene extends Scene {
console.warn(
'Invalid repetitionCount value, repetitionCount is set to Infinity');
}
} else if (typeof repetitionCount === 'number' && repetitionCount < 1) {
repetitionCount = 1;
}

if (repetitionCount === 1 && loopMode !== LoopOnce) {
Expand Down Expand Up @@ -870,12 +878,13 @@ export class ModelScene extends Scene {
const action = this.mixer.existingAction(animationClip) ||
this.mixer.clipAction(animationClip, this);

if (!this.appendedAnimations.includes(name)) {
this.element[$scene].appendedAnimations.push(name);
}
const currentTimeScale = action.timeScale;

action.stop();
action.setLoop(loopMode, repetitionCount);
if (needsToStop && this.appendedAnimations.includes(name)) {
if (!this.markedAnimations.map(e => e.name).includes(name)) {
this.markedAnimations.push({name, loopMode, repetitionCount});
}
}

if (typeof fade === 'boolean' && fade) {
action.fadeIn(defaultFade);
Expand All @@ -888,16 +897,26 @@ export class ModelScene extends Scene {
}

if (typeof warp === 'boolean' && warp) {
action.warp(0, timeScale, defaultFade);
action.warp(
relativeWarp ? currentTimeScale : 0, timeScale, defaultFade);
} else if (typeof warp === 'number') {
action.warp(0, timeScale, Math.max(warp, 0));
action.warp(
relativeWarp ? currentTimeScale : 0, timeScale, Math.max(warp, 0));
} else {
action.timeScale = Math.min(Math.max(timeScale, 0), 1);
action.timeScale = timeScale;
}

action.enabled = true;
action.clampWhenFinished = true;
action.play();
if (!action.isRunning()) {
action.setLoop(loopMode, repetitionCount);
action.paused = false;
action.enabled = true;
action.clampWhenFinished = true;
action.play();
}

if (!this.appendedAnimations.includes(name)) {
this.element[$scene].appendedAnimations.push(name);
}
} catch (error) {
console.error(error);
}
Expand Down Expand Up @@ -958,6 +977,38 @@ export class ModelScene extends Scene {
}
}

updateAnimationLoop(
name: string = '', loopMode: AnimationActionLoopStyles = LoopRepeat,
repetitionCount: number = Infinity) {
if (this._currentGLTF == null || name === this.element.animationName) {
return;
}
const {animations} = this;
if (animations == null || animations.length === 0) {
return;
}

let animationClip = null;

if (name) {
animationClip = this.animationsByName.get(name);
}

if (animationClip == null) {
return;
}

try {
const action = this.mixer.existingAction(animationClip) ||
this.mixer.clipAction(animationClip, this);
action.stop();
action.setLoop(loopMode, repetitionCount);
action.play();
} catch (error) {
console.error(error);
}
}

stopAnimation() {
this.currentAnimationAction = null;
this.mixer.stopAllAction();
Expand Down
4 changes: 2 additions & 2 deletions packages/modelviewer.dev/data/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -1030,9 +1030,9 @@
"description": "Causes animations to be paused. If you want to reset the current animation to the beginning, you should also set the <span class='attribute'>currentTime</span> property to 0."
},
{
"name": "appendAnimation(animationName, options: {repetitions, pingpong, weight, timeScale, fade, warp})",
"name": "appendAnimation(animationName, options: {repetitions, pingpong, weight, timeScale, fade, warp, relativeWarp})",
"htmlName": "appendAnimation",
"description": "Causes an animation to run simultaneously with and blend into the current animation. If you want to get the appended animations, you can use <span class='attribute'>appendedAnimations</span> property. You can specify the number of repetitions of the animation by setting the number of <i>repetitions</i> to any value greater than 0 (defaults to Infinity). Also if you set <i>pingpong</i> to true, alternately playing forward and backward (defaults to false). <i>weight</i> The degree of influence of this animation (in the interval [0, 1]), Values between 0 (no impact) and 1 (full impact) can be used to blend between several actions (defaults to 1). <i>timeScale</i> option can be used to slow down, speed up or play backwards animations. For example, if you set it to 0.5, the animation will play at half speed (defaults to 1). The <i>fade</i> option causes the weight of this animation to gradually increase from 0 to 1, The value of this option can be a boolean or float greater than 0 (in seconds), If the value is true, default duration (1.25 second) is applied (defaults to false). The <i>warp</i> option causes change playback speed, within the passed time interval, by modifying timeScale gradually from 0 to current timeScale value, the value of this option can be a boolean or float greater than 0 (in seconds), If the value is true, default duration (1.25 second) is applied (defaults to false).",
"description": "Causes an animation to run simultaneously with and blend into the current animation. If you want to get the appended animations, you can use <span class='attribute'>appendedAnimations</span> property. You can specify the number of repetitions of the animation by setting the number of <i>repetitions</i> to any value greater than 0 (defaults to Infinity). Also if you set <i>pingpong</i> to true, alternately playing forward and backward (defaults to false). <i>weight</i> The degree of influence of this animation (in the interval [0, 1]), Values between 0 (no impact) and 1 (full impact) can be used to blend between several actions (defaults to 1). <i>timeScale</i> option can be used to slow down, speed up or play backwards animations. For example, if you set it to 0.5, the animation will play at half speed (defaults to 1). The <i>fade</i> option causes the weight of this animation to gradually increase from 0 to 1, The value of this option can be a boolean or float greater than 0 (in seconds), If the value is true, default duration (1.25 second) is applied (defaults to false). The <i>warp</i> option causes change playback speed, within the passed time interval, by modifying timeScale gradually from 0 to current timeScale value, the value of this option can be a boolean or float greater than 0 (in seconds), If the value is true, default duration (1.25 second) is applied (defaults to false), The <i><relativeWarp/i> option changes the warp from the current timeScale to the new value. The value of this option is boolean. If set to false, the timeScale starts from 0 until the entered value (defaults to true).",
"links": [
"<a href=\"../examples/animation/#appendAnimation\">Append animation example</a>"
]
Expand Down

0 comments on commit e2dd816

Please sign in to comment.