Skip to content

Commit

Permalink
Merge pull request #14233 from Popov72/animgroup-helpers-inspector
Browse files Browse the repository at this point in the history
Animation groups: Add ClipKeys helper + update inspector
  • Loading branch information
sebavan authored Aug 30, 2023
2 parents e71b9fa + 53743b2 commit 21d5b21
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 13 deletions.
5 changes: 3 additions & 2 deletions packages/dev/core/src/Animations/animation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1140,9 +1140,10 @@ export class Animation {
/**
* Sets the key frames of the animation
* @param values The animation key frames to set
* @param dontClone Whether to clone the keys or not (default is false, so the array of keys is cloned)
*/
public setKeys(values: Array<IAnimationKey>): void {
this._keys = values.slice(0);
public setKeys(values: Array<IAnimationKey>, dontClone = false): void {
this._keys = !dontClone ? values.slice(0) : values;
}

/**
Expand Down
131 changes: 120 additions & 11 deletions packages/dev/core/src/Animations/animationGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ export class AnimationGroup implements IDisposable {
private _isAdditive = false;
private _weight = -1;
private _playOrder = 0;
private _enableBlending: Nullable<boolean> = null;
private _blendingSpeed: Nullable<number> = null;

/** @internal */
public _parentContainer: Nullable<AbstractScene> = null;
Expand Down Expand Up @@ -320,22 +322,45 @@ export class AnimationGroup implements IDisposable {

/**
* Allows the animations of the animation group to blend with current running animations
* Note: this method should be called after all targeted animations have been added to the group
* @param blendingSpeed defines the blending speed to use
* Note that a null value means that each animation will use their own existing blending configuration (Animation.enableBlending)
*/
public enableBlending(blendingSpeed: number) {
for (let i = 0; i < this._targetedAnimations.length; ++i) {
this._targetedAnimations[i].animation.enableBlending = true;
this._targetedAnimations[i].animation.blendingSpeed = blendingSpeed;
public get enableBlending() {
return this._enableBlending;
}

public set enableBlending(value: Nullable<boolean>) {
if (this._enableBlending === value) {
return;
}

this._enableBlending = value;

if (value !== null) {
for (let i = 0; i < this._targetedAnimations.length; ++i) {
this._targetedAnimations[i].animation.enableBlending = value;
}
}
}

/**
* Disable animation blending
* Gets or sets the animation blending speed
* Note that a null value means that each animation will use their own existing blending configuration (Animation.blendingSpeed)
*/
public disableBlending() {
for (let i = 0; i < this._targetedAnimations.length; ++i) {
this._targetedAnimations[i].animation.enableBlending = false;
public get blendingSpeed() {
return this._blendingSpeed;
}

public set blendingSpeed(value: Nullable<number>) {
if (this._blendingSpeed === value) {
return;
}

this._blendingSpeed = value;

if (value !== null) {
for (let i = 0; i < this._targetedAnimations.length; ++i) {
this._targetedAnimations[i].animation.blendingSpeed = value;
}
}
}

Expand All @@ -355,7 +380,7 @@ export class AnimationGroup implements IDisposable {
weight = weight ?? animationGroups[0].weight;

let beginFrame = Number.MAX_VALUE;
let endFrame = Number.MIN_VALUE;
let endFrame = -Number.MAX_VALUE;

if (normalize) {
for (const animationGroup of animationGroups) {
Expand Down Expand Up @@ -432,6 +457,14 @@ export class AnimationGroup implements IDisposable {
this._to = keys[keys.length - 1].frame;
}

if (this._enableBlending !== null) {
animation.enableBlending = this._enableBlending;
}

if (this._blendingSpeed !== null) {
animation.blendingSpeed = this._blendingSpeed;
}

this._targetedAnimations.push(targetedAnimation);

return targetedAnimation;
Expand Down Expand Up @@ -904,6 +937,82 @@ export class AnimationGroup implements IDisposable {
return animationGroup;
}

/**
* Creates a new animation, keeping only the keys that are inside a given range
* @param animationGroup defines the animation group on which to operate
* @param fromKey defines the lower bound of the range
* @param toKey defines the upper bound of the range
* @param name defines the name of the new animation group. If not provided, use the same name as animationGroup
* @returns a new animation group stripped from all the keys outside the given range
*/
public static ClipKeys(sourceAnimationGroup: AnimationGroup, fromKey: number, toKey: number, name?: string): AnimationGroup {
const animationGroup = sourceAnimationGroup.clone(name || sourceAnimationGroup.name);

return this.ClipKeysInPlace(animationGroup, fromKey, toKey);
}

/**
* Updates an existing animation, keeping only the keys that are inside a given range
* @param animationGroup defines the animation group on which to operate
* @param fromKey defines the lower bound of the range
* @param toKey defines the upper bound of the range
* @returns the source animationGroup stripped from all the keys outside the given range
*/
public static ClipKeysInPlace(animationGroup: AnimationGroup, fromKey: number, toKey: number): AnimationGroup {
let from = Number.MAX_VALUE;
let to = -Number.MAX_VALUE;

const targetedAnimations = animationGroup.targetedAnimations;
for (let index = 0; index < targetedAnimations.length; index++) {
const targetedAnimation = targetedAnimations[index];
const animation = targetedAnimation.animation.clone();
const keys = animation.getKeys();
const newKeys: IAnimationKey[] = [];

let startFrame = Number.MAX_VALUE;
for (let k = 0; k < keys.length; k++) {
if (k >= fromKey && k <= toKey) {
const key = keys[k];
const newKey: IAnimationKey = {
frame: key.frame,
value: key.value,
inTangent: key.inTangent,
outTangent: key.outTangent,
interpolation: key.interpolation,
lockedTangent: key.lockedTangent,
};
if (startFrame === Number.MAX_VALUE) {
startFrame = newKey.frame;
}
newKey.frame -= startFrame;
newKeys.push(newKey);
}
}

if (newKeys.length === 0) {
targetedAnimations.splice(index, 1);
index--;
continue;
}

if (from > newKeys[0].frame) {
from = newKeys[0].frame;
}

if (to < newKeys[newKeys.length - 1].frame) {
to = newKeys[newKeys.length - 1].frame;
}

animation.setKeys(newKeys, true);
targetedAnimation.animation = animation;
}

animationGroup._from = from;
animationGroup._to = to;

return animationGroup;
}

/**
* Returns the string "AnimationGroup"
* @returns "AnimationGroup"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import type { GlobalState } from "../../../../globalState";
import { TextInputLineComponent } from "shared-ui-components/lines/textInputLineComponent";
import { Context } from "./curveEditor/context";
import { AnimationCurveEditorComponent } from "./curveEditor/animationCurveEditorComponent";
import { FloatLineComponent } from "shared-ui-components/lines/floatLineComponent";
import { CheckBoxLineComponent } from "shared-ui-components/lines/checkBoxLineComponent";

interface IAnimationGroupGridComponentProps {
globalState: GlobalState;
Expand Down Expand Up @@ -155,6 +157,7 @@ export class AnimationGroupGridComponent extends React.Component<IAnimationGroup
</LineContainerComponent>
<LineContainerComponent title="CONTROLS">
<ButtonLineComponent label={playButtonText} onClick={() => this.playOrPause()} />
<ButtonLineComponent label="Stop" onClick={() => this.props.animationGroup.stop()} />
<SliderLineComponent
lockObject={this.props.lockObject}
label="Speed ratio"
Expand All @@ -175,6 +178,43 @@ export class AnimationGroupGridComponent extends React.Component<IAnimationGroup
directValue={this.state.currentFrame}
onInput={(value) => this.onCurrentFrameChange(value)}
/>
<CheckBoxLineComponent
label="Blending"
target={animationGroup}
propertyName="enableBlending"
onPropertyChangedObservable={this.props.onPropertyChangedObservable}
/>
<SliderLineComponent
lockObject={this.props.lockObject}
label="Blending speed"
minimum={0}
maximum={1}
step={0.01}
target={animationGroup}
propertyName="blendingSpeed"
onPropertyChangedObservable={this.props.onPropertyChangedObservable}
/>
<CheckBoxLineComponent
label="Is additive"
target={animationGroup}
propertyName="isAdditive"
onPropertyChangedObservable={this.props.onPropertyChangedObservable}
/>
<FloatLineComponent
lockObject={this.props.lockObject}
label="Weight"
target={animationGroup}
propertyName="weight"
onPropertyChangedObservable={this.props.onPropertyChangedObservable}
/>
<FloatLineComponent
lockObject={this.props.lockObject}
label="Play order"
target={animationGroup}
propertyName="playOrder"
isInteger={true}
onPropertyChangedObservable={this.props.onPropertyChangedObservable}
/>
</LineContainerComponent>
<LineContainerComponent title="INFOS">
<TextLineComponent label="Animation count" value={animationGroup.targetedAnimations.length.toString()} />
Expand Down

0 comments on commit 21d5b21

Please sign in to comment.