Skip to content

Commit

Permalink
Fix crossFade error caused by transitionOffsetTime and clipStartTime (#…
Browse files Browse the repository at this point in the history
…2411)

* fix: crossFade error caused by transitionOffsetTime and clipStartTime
  • Loading branch information
luzhuang authored Oct 30, 2024
1 parent 7226618 commit ebd6cbf
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 27 deletions.
33 changes: 16 additions & 17 deletions packages/core/src/animation/Animator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -570,9 +570,8 @@ export class Animator extends Component {

let playCostTime: number;
if (transition) {
const clipDuration = state.clip.length;
const clipEndTime = state.clipEndTime * clipDuration;
const exitTime = transition.exitTime * state._getDuration();
const clipEndTime = state._getClipActualEndTime();
const exitTime = transition.exitTime * state._getDuration() + state._getClipActualStartTime();

if (isForwards) {
if (exitTime < lastClipTime) {
Expand All @@ -581,7 +580,7 @@ export class Animator extends Component {
playCostTime = exitTime - lastClipTime;
}
} else {
const startTime = state.clipStartTime * clipDuration;
const startTime = state._getClipActualStartTime();
if (lastClipTime < exitTime) {
playCostTime = clipEndTime - exitTime + lastClipTime - startTime;
} else {
Expand Down Expand Up @@ -667,15 +666,15 @@ export class Animator extends Component {

let dstPlayCostTime: number;
if (destPlayData.isForwards) {
// The time that has been played
const playedTime = destPlayData.playedTime;
dstPlayCostTime =
lastDestClipTime + dstPlayDeltaTime > transitionDuration
? transitionDuration - lastDestClipTime
: dstPlayDeltaTime;
playedTime + dstPlayDeltaTime > transitionDuration ? transitionDuration - playedTime : dstPlayDeltaTime;
} else {
// The time that has been played
const playedTime = destStateDuration - lastDestClipTime;
const playedTime = destPlayData.playedTime;
dstPlayCostTime =
// -actualDestDeltaTime: The time that will be played, negative are meant to make ite be a periods
// -dstPlayDeltaTime: The time that will be played, negative are meant to make it be a periods
// > transition: The time that will be played is enough to finish the transition
playedTime - dstPlayDeltaTime > transitionDuration
? // Negative number is used to convert a time period into a reverse deltaTime.
Expand All @@ -690,7 +689,7 @@ export class Animator extends Component {
srcPlayData.update(srcPlayCostTime);
destPlayData.update(dstPlayCostTime);

let crossWeight = Math.abs(destPlayData.frameTime) / transitionDuration;
let crossWeight = Math.abs(destPlayData.playedTime) / transitionDuration;
(crossWeight >= 1.0 - MathUtil.zeroTolerance || transitionDuration === 0) && (crossWeight = 1.0);

const crossFadeFinished = crossWeight === 1.0;
Expand Down Expand Up @@ -794,11 +793,13 @@ export class Animator extends Component {

let dstPlayCostTime: number;
if (destPlayData.isForwards) {
// The time that has been played
const playedTime = destPlayData.playedTime;
dstPlayCostTime =
lastDestClipTime + playDeltaTime > transitionDuration ? transitionDuration - lastDestClipTime : playDeltaTime;
playedTime + playDeltaTime > transitionDuration ? transitionDuration - playedTime : playDeltaTime;
} else {
// The time that has been played
const playedTime = stateDuration - lastDestClipTime;
const playedTime = destPlayData.playedTime;
dstPlayCostTime =
// -actualDestDeltaTime: The time that will be played, negative are meant to make ite be a periods
// > transition: The time that will be played is enough to finish the transition
Expand All @@ -813,7 +814,7 @@ export class Animator extends Component {

destPlayData.update(dstPlayCostTime);

let crossWeight = Math.abs(destPlayData.frameTime) / transitionDuration;
let crossWeight = Math.abs(destPlayData.playedTime) / transitionDuration;
(crossWeight >= 1.0 - MathUtil.zeroTolerance || transitionDuration === 0) && (crossWeight = 1.0);

const crossFadeFinished = crossWeight === 1.0;
Expand Down Expand Up @@ -1086,10 +1087,9 @@ export class Animator extends Component {
): AnimatorStateTransition {
const { state } = playState;
let transitionIndex = playState.currentTransitionIndex;
const duration = state._getDuration();
for (let n = transitions.length; transitionIndex < n; transitionIndex++) {
const transition = transitions[transitionIndex];
const exitTime = transition.exitTime * duration;
const exitTime = transition.exitTime * state._getDuration() + state._getClipActualStartTime();
if (exitTime > curClipTime) {
break;
}
Expand Down Expand Up @@ -1120,10 +1120,9 @@ export class Animator extends Component {
): AnimatorStateTransition {
const { state } = playState;
let transitionIndex = playState.currentTransitionIndex;
const duration = playState.state._getDuration();
for (; transitionIndex >= 0; transitionIndex--) {
const transition = transitions[transitionIndex];
const exitTime = transition.exitTime * duration;
const exitTime = transition.exitTime * state._getDuration() + state._getClipActualStartTime();
if (exitTime < curClipTime) {
break;
}
Expand Down
14 changes: 14 additions & 0 deletions packages/core/src/animation/AnimatorState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,4 +232,18 @@ export class AnimatorState {
}
}
}

/**
* @internal
*/
_getClipActualStartTime(): number {
return this._clipStartTime * this.clip.length;
}

/**
* @internal
*/
_getClipActualEndTime(): number {
return this._clipEndTime * this.clip.length;
}
}
10 changes: 6 additions & 4 deletions packages/core/src/animation/internal/AnimatorStatePlayData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,20 @@ import { AnimatorStateData } from "./AnimatorStateData";
export class AnimatorStatePlayData {
state: AnimatorState;
stateData: AnimatorStateData;
frameTime: number;
playedTime: number;
playState: AnimatorStatePlayState;
clipTime: number;
currentEventIndex: number;
currentTransitionIndex: number;
isForwards = true;
offsetFrameTime: number;

private _changedOrientation = false;

reset(state: AnimatorState, stateData: AnimatorStateData, offsetFrameTime: number): void {
this.state = state;
this.frameTime = offsetFrameTime;
this.playedTime = 0;
this.offsetFrameTime = offsetFrameTime;
this.stateData = stateData;
this.playState = AnimatorStatePlayState.UnStarted;
this.clipTime = state.clipStartTime * state.clip.length;
Expand All @@ -41,9 +43,9 @@ export class AnimatorStatePlayData {
}

update(deltaTime: number): void {
this.frameTime += deltaTime;
this.playedTime += deltaTime;
const state = this.state;
let time = this.frameTime;
let time = this.playedTime + this.offsetFrameTime;
const duration = state._getDuration();
this.playState = AnimatorStatePlayState.Playing;
if (state.wrapMode === WrapMode.Loop) {
Expand Down
53 changes: 47 additions & 6 deletions tests/src/core/Animator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,24 +81,24 @@ describe("Animator test", function () {
const speed = 1;
let expectedSpeed = speed * 0.5;
animator.speed = expectedSpeed;
let lastFrameTime = srcPlayData.frameTime;
let lastFrameTime = srcPlayData.playedTime;
// @ts-ignore
animator.engine.time._frameCount++;
animator.update(5);
expect(animator.speed).to.eq(expectedSpeed);
expect(srcPlayData.frameTime).to.eq(lastFrameTime + 5 * expectedSpeed);
expect(srcPlayData.playedTime).to.eq(lastFrameTime + 5 * expectedSpeed);
expectedSpeed = speed * 2;
animator.speed = expectedSpeed;
lastFrameTime = srcPlayData.frameTime;
lastFrameTime = srcPlayData.playedTime;
animator.update(10);
expect(animator.speed).to.eq(expectedSpeed);
expect(srcPlayData.frameTime).to.eq(lastFrameTime + 10 * expectedSpeed);
expect(srcPlayData.playedTime).to.eq(lastFrameTime + 10 * expectedSpeed);
expectedSpeed = speed * 0;
animator.speed = expectedSpeed;
lastFrameTime = srcPlayData.frameTime;
lastFrameTime = srcPlayData.playedTime;
animator.update(15);
expect(animator.speed).to.eq(expectedSpeed);
expect(srcPlayData.frameTime).to.eq(lastFrameTime + 15 * expectedSpeed);
expect(srcPlayData.playedTime).to.eq(lastFrameTime + 15 * expectedSpeed);
});

it("play animation", () => {
Expand Down Expand Up @@ -567,6 +567,47 @@ describe("Animator test", function () {
expect(animator.getCurrentAnimatorState(0).name).to.eq("Survey");
});

it("transitionOffset", () => {
const walkState = animator.findAnimatorState("Walk");
walkState.clearTransitions();
const runState = animator.findAnimatorState("Run");
runState.clearTransitions();
const toRunTransition = walkState.addTransition(runState);
toRunTransition.exitTime = 0;
toRunTransition.duration = 1;
toRunTransition.offset = 0.5;
animator.play("Walk");
// @ts-ignore
animator.engine.time._frameCount++;
animator.update(0.01);

const destPlayData = animator["_animatorLayersData"][0].destPlayData;
const destState = destPlayData.state;
const transitionDuration = toRunTransition.duration * destState._getDuration();
const crossWeight = animator["_animatorLayersData"][0].destPlayData.playedTime / transitionDuration;
expect(crossWeight).to.lessThan(0.01);
});

it("clipStartTime crossFade", () => {
const walkState = animator.findAnimatorState("Walk");
walkState.wrapMode = WrapMode.Once;
walkState.clipStartTime = 0.8;
walkState.clearTransitions();
const runState = animator.findAnimatorState("Run");
runState.clearTransitions();
const toRunTransition = walkState.addTransition(runState);
toRunTransition.exitTime = 0.5;
toRunTransition.duration = 1;
runState.clipStartTime = 0.5;
animator.play("Walk");
// @ts-ignore
animator.engine.time._frameCount++;
animator.update(0.1);

const destPlayData = animator["_animatorLayersData"][0].destPlayData;
expect(destPlayData.state?.name).to.eq("Run");
});

it("change state in one update", () => {
const animatorController = new AnimatorController(engine);
const layer = new AnimatorControllerLayer("layer");
Expand Down

0 comments on commit ebd6cbf

Please sign in to comment.