|
@@ -17,6 +17,7 @@ import { Scene } from "babylonjs/scene";
|
|
|
import { ButtonLineComponent } from '../../../lines/buttonLineComponent';
|
|
|
import { IAnimatable } from 'babylonjs/Animations/animatable.interface';
|
|
|
import { TargetedAnimation } from "babylonjs/Animations/animationGroup";
|
|
|
+import { Nullable } from 'babylonjs/types';
|
|
|
|
|
|
require("./curveEditor.scss");
|
|
|
|
|
@@ -34,7 +35,6 @@ interface ICanvasAxis {
|
|
|
}
|
|
|
|
|
|
export class AnimationCurveEditorComponent extends React.Component<IAnimationCurveEditorComponentProps, {
|
|
|
- animations: Animation[],
|
|
|
animationName: string,
|
|
|
animationType: string,
|
|
|
animationTargetProperty: string,
|
|
@@ -49,12 +49,14 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
|
|
|
isFlatTangentMode: boolean,
|
|
|
isTangentMode: boolean,
|
|
|
isBrokenMode: boolean,
|
|
|
+ lerpMode: boolean,
|
|
|
scale: number,
|
|
|
playheadOffset: number,
|
|
|
notification: string,
|
|
|
currentPoint: SVGPoint | undefined,
|
|
|
lastFrame: number,
|
|
|
- playheadPos: number
|
|
|
+ playheadPos: number,
|
|
|
+ isPlaying: boolean
|
|
|
}> {
|
|
|
|
|
|
// Height scale *Review this functionaliy
|
|
@@ -62,7 +64,6 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
|
|
|
// Canvas Length *Review this functionality
|
|
|
readonly _entityName: string;
|
|
|
readonly _canvasLength: number = 20;
|
|
|
- private _newAnimations: Animation[] = [];
|
|
|
private _svgKeyframes: IKeyframeSvgPoint[] = [];
|
|
|
private _frames: Vector2[] = [];
|
|
|
private _isPlaying: boolean = false;
|
|
@@ -82,12 +83,15 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
|
|
|
|
|
|
let initialSelection;
|
|
|
let initialPathData;
|
|
|
- if (this.props.entity instanceof TargetedAnimation){
|
|
|
+ let initialLerpMode;
|
|
|
+ if (this.props.entity instanceof TargetedAnimation) {
|
|
|
this._isTargetedAnimation = true;
|
|
|
initialSelection = this.props.entity.animation;
|
|
|
+ initialLerpMode = this.analizeAnimation(this.props.entity.animation);
|
|
|
initialPathData = this.getPathData(this.props.entity.animation);
|
|
|
} else {
|
|
|
this._isTargetedAnimation = false;
|
|
|
+ initialLerpMode = this.analizeAnimation(this.props.entity.animations && this.props.entity.animations[0]);
|
|
|
initialSelection = this.props.entity.animations !== null ? this.props.entity.animations[0] : null;
|
|
|
initialPathData = this.props.entity.animations !== null ? this.getPathData(this.props.entity.animations[0]) : "";
|
|
|
}
|
|
@@ -95,7 +99,6 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
|
|
|
// will update this until we have a top scroll/zoom feature
|
|
|
let valueInd = [2, 1.8, 1.6, 1.4, 1.2, 1, 0.8, 0.6, 0.4, 0.2, 0];
|
|
|
this.state = {
|
|
|
- animations: this._newAnimations,
|
|
|
selected: initialSelection,
|
|
|
isOpen: true,
|
|
|
currentPathData: initialPathData,
|
|
@@ -108,6 +111,7 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
|
|
|
isFlatTangentMode: false,
|
|
|
isTangentMode: false,
|
|
|
isBrokenMode: false,
|
|
|
+ lerpMode: initialLerpMode,
|
|
|
playheadOffset: this._graphCanvas.current ? (this._graphCanvas.current.children[1].clientWidth) / (this._canvasLength * 10) : 0,
|
|
|
frameAxisLength: (new Array(this._canvasLength)).fill(0).map((s, i) => { return { value: i * 10, label: i * 10 } }),
|
|
|
valueAxisLength: (new Array(10)).fill(0).map((s, i) => { return { value: i * 10, label: valueInd[i] } }),
|
|
@@ -116,6 +120,7 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
|
|
|
currentPoint: undefined,
|
|
|
scale: 1,
|
|
|
playheadPos: 0,
|
|
|
+ isPlaying: this.isAnimationPlaying()
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -151,9 +156,8 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
|
|
|
}
|
|
|
|
|
|
setAxesLength() {
|
|
|
-
|
|
|
- let length = Math.round(this._canvasLength * this.state.scale);
|
|
|
-
|
|
|
+
|
|
|
+ let length = Math.round(this._canvasLength * this.state.scale);// Check Undefined, or NaN
|
|
|
let highestFrame = 100;
|
|
|
if (this.state.selected !== null) {
|
|
|
highestFrame = this.state.selected.getHighestFrame();
|
|
@@ -220,6 +224,7 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
|
|
|
element = <li className={this.state.selected && this.state.selected.name === animation.name ? 'active' : ''} key={i} onClick={() => this.selectAnimation(animation)}>
|
|
|
<p>{animation.name}
|
|
|
<span>{animation.targetProperty}</span></p>
|
|
|
+ {!(this.props.entity instanceof TargetedAnimation) ? this.state.selected && this.state.selected.name === animation.name ? <ButtonLineComponent label={"Remove"} onClick={() => this.deleteAnimation()} /> : null : null}
|
|
|
</li>
|
|
|
break;
|
|
|
case Animation.ANIMATIONTYPE_VECTOR2:
|
|
@@ -314,6 +319,19 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
|
|
|
|
|
|
}
|
|
|
|
|
|
+ deleteAnimation() {
|
|
|
+ let currentSelected = this.state.selected;
|
|
|
+ if (this.props.entity instanceof TargetedAnimation) {
|
|
|
+ console.log("no animation remove allowed");
|
|
|
+ } else {
|
|
|
+ let animations = (this.props.entity as IAnimatable).animations;
|
|
|
+ if (animations) {
|
|
|
+ let updatedAnimations = animations.filter(anim => anim !== currentSelected);
|
|
|
+ (this.props.entity as IAnimatable).animations = updatedAnimations as Nullable<Animation[]>;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
addAnimation() {
|
|
|
if (this.state.animationName != "" && this.state.animationTargetProperty != "") {
|
|
|
|
|
@@ -411,24 +429,39 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
- let animation = new Animation(this.state.animationName, this.state.animationTargetProperty, 30, animationDataType);
|
|
|
+ let alreadyAnimatedProperty = (this.props.entity as IAnimatable).animations?.find(anim =>
|
|
|
+ anim.targetProperty === this.state.animationTargetProperty
|
|
|
+ , this);
|
|
|
|
|
|
- // Start with two keyframes
|
|
|
- var keys = [];
|
|
|
- keys.push({
|
|
|
- frame: 0,
|
|
|
- value: startValue,
|
|
|
- outTangent: outTangent
|
|
|
- });
|
|
|
+ let alreadyAnimationName = (this.props.entity as IAnimatable).animations?.find(anim =>
|
|
|
+ anim.name === this.state.animationName
|
|
|
+ , this);
|
|
|
|
|
|
- keys.push({
|
|
|
- inTangent: inTangent,
|
|
|
- frame: 100,
|
|
|
- value: endValue
|
|
|
- });
|
|
|
+ if (alreadyAnimatedProperty) {
|
|
|
+ this.setState({ notification: `The property "${this.state.animationTargetProperty}" already has an animation` });
|
|
|
+ } else if (alreadyAnimationName) {
|
|
|
+ this.setState({ notification: `There is already an animation with the name: "${this.state.animationName}"` });
|
|
|
+ } else {
|
|
|
+
|
|
|
+ let animation = new Animation(this.state.animationName, this.state.animationTargetProperty, 30, animationDataType);
|
|
|
+
|
|
|
+ // Start with two keyframes
|
|
|
+ var keys = [];
|
|
|
+ keys.push({
|
|
|
+ frame: 0,
|
|
|
+ value: startValue,
|
|
|
+ outTangent: outTangent
|
|
|
+ });
|
|
|
|
|
|
- animation.setKeys(keys);
|
|
|
- (this.props.entity as IAnimatable).animations?.push(animation);
|
|
|
+ keys.push({
|
|
|
+ inTangent: inTangent,
|
|
|
+ frame: 100,
|
|
|
+ value: endValue
|
|
|
+ });
|
|
|
+
|
|
|
+ animation.setKeys(keys);
|
|
|
+ (this.props.entity as IAnimatable).animations?.push(animation);
|
|
|
+ }
|
|
|
|
|
|
} else {
|
|
|
this.setState({ notification: `The property "${this.state.animationTargetProperty}" is not a "${this.state.animationType}" type` });
|
|
@@ -492,17 +525,17 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
|
|
|
keys[index].value = ((this._heightScale - updatedSvgKeyFrame.keyframePoint.y) / this._heightScale) * 2; // this value comes inverted svg from 0 = 100 to 100 = 0
|
|
|
|
|
|
|
|
|
- if (!this.state.isBrokenMode) {
|
|
|
- if (updatedSvgKeyFrame.isLeftActive) {
|
|
|
+ if (updatedSvgKeyFrame.isLeftActive) {
|
|
|
|
|
|
- if (updatedSvgKeyFrame.leftControlPoint !== null) {
|
|
|
- // Rotate
|
|
|
- let updatedValue = ((this._heightScale - updatedSvgKeyFrame.leftControlPoint.y) / this._heightScale) * 2;
|
|
|
+ if (updatedSvgKeyFrame.leftControlPoint !== null) {
|
|
|
+ // Rotate
|
|
|
+ let updatedValue = ((this._heightScale - updatedSvgKeyFrame.leftControlPoint.y) / this._heightScale) * 2;
|
|
|
|
|
|
- let keyframeValue = ((this._heightScale - updatedSvgKeyFrame.keyframePoint.y) / this._heightScale) * 2;
|
|
|
+ let keyframeValue = ((this._heightScale - updatedSvgKeyFrame.keyframePoint.y) / this._heightScale) * 2;
|
|
|
|
|
|
- keys[index].inTangent = keyframeValue - updatedValue;
|
|
|
+ keys[index].inTangent = keyframeValue - updatedValue;
|
|
|
|
|
|
+ if (!this.state.isBrokenMode) {
|
|
|
// Right control point if exists
|
|
|
if (updatedSvgKeyFrame.rightControlPoint !== null) {
|
|
|
// Sets opposite value
|
|
@@ -510,17 +543,19 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- if (updatedSvgKeyFrame.isRightActive) {
|
|
|
+ if (updatedSvgKeyFrame.isRightActive) {
|
|
|
|
|
|
- if (updatedSvgKeyFrame.rightControlPoint !== null) {
|
|
|
- // Rotate
|
|
|
- let updatedValue = ((this._heightScale - updatedSvgKeyFrame.rightControlPoint.y) / this._heightScale) * 2;
|
|
|
+ if (updatedSvgKeyFrame.rightControlPoint !== null) {
|
|
|
+ // Rotate
|
|
|
+ let updatedValue = ((this._heightScale - updatedSvgKeyFrame.rightControlPoint.y) / this._heightScale) * 2;
|
|
|
|
|
|
- let keyframeValue = ((this._heightScale - updatedSvgKeyFrame.keyframePoint.y) / this._heightScale) * 2;
|
|
|
+ let keyframeValue = ((this._heightScale - updatedSvgKeyFrame.keyframePoint.y) / this._heightScale) * 2;
|
|
|
|
|
|
- keys[index].outTangent = keyframeValue - updatedValue;
|
|
|
+ keys[index].outTangent = keyframeValue - updatedValue;
|
|
|
|
|
|
+ if (!this.state.isBrokenMode) {
|
|
|
if (updatedSvgKeyFrame.leftControlPoint !== null) { // Sets opposite value
|
|
|
keys[index].inTangent = keys[index].outTangent * -1
|
|
|
}
|
|
@@ -528,6 +563,7 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+
|
|
|
animation.setKeys(keys);
|
|
|
|
|
|
this.selectAnimation(animation);
|
|
@@ -587,6 +623,13 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ setLerpMode() {
|
|
|
+ if (this.state.selected !== null) {
|
|
|
+ let animation = this.state.selected;
|
|
|
+ this.setState({ lerpMode: !this.state.lerpMode }, () => this.selectAnimation(animation));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
addKeyframeClick() {
|
|
|
|
|
|
if (this.state.selected !== null) {
|
|
@@ -771,16 +814,35 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
|
|
|
// START OF LINE/CURVE
|
|
|
let data: string | undefined = `M${startKey.frame}, ${this._heightScale - (startKey.value * middle)}`;
|
|
|
|
|
|
- if (this.getAnimationData(animation).usesTangents) {
|
|
|
- data = this.curvePathWithTangents(keyframes, data, middle, animation.dataType);
|
|
|
+ if (this.state && this.state.lerpMode) {
|
|
|
+ data = this.linearInterpolation(keyframes, data, middle);
|
|
|
} else {
|
|
|
- console.log("no tangents in this animation");
|
|
|
- const { easingMode, easingType } = this.getAnimationProperties(animation);
|
|
|
- if (easingType === undefined && easingMode === undefined) {
|
|
|
- data = this.linearInterpolation(keyframes, data, middle);
|
|
|
+ if (this.getAnimationData(animation).usesTangents) {
|
|
|
+ data = this.curvePathWithTangents(keyframes, data, middle, animation.dataType);
|
|
|
} else {
|
|
|
- let easingFunction = animation.getEasingFunction();
|
|
|
- data = this.curvePath(keyframes, data, middle, easingFunction as EasingFunction)
|
|
|
+ const { easingMode, easingType } = this.getAnimationProperties(animation);
|
|
|
+ if (easingType !== undefined && easingMode !== undefined) {
|
|
|
+ let easingFunction = animation.getEasingFunction();
|
|
|
+ data = this.curvePath(keyframes, data, middle, easingFunction as EasingFunction)
|
|
|
+ } else {
|
|
|
+ if (this.state !== undefined) {
|
|
|
+ let emptyTangents = keyframes.map((kf, i) => {
|
|
|
+ if (i === 0) {
|
|
|
+ kf.outTangent = 0;
|
|
|
+ } else if (i === keyframes.length - 1) {
|
|
|
+ kf.inTangent = 0;
|
|
|
+ } else {
|
|
|
+ kf.inTangent = 0;
|
|
|
+ kf.outTangent = 0;
|
|
|
+ }
|
|
|
+ return kf;
|
|
|
+ });
|
|
|
+ data = this.curvePathWithTangents(emptyTangents, data, middle, animation.dataType);
|
|
|
+ } else {
|
|
|
+ data = this.linearInterpolation(keyframes, data, middle);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -1067,7 +1129,7 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
|
|
|
*/
|
|
|
selectAnimation(animation: Animation) {
|
|
|
|
|
|
- this.isAnimationPlaying();
|
|
|
+ this.playStopAnimation();
|
|
|
|
|
|
this._svgKeyframes = [];
|
|
|
|
|
@@ -1084,17 +1146,69 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
|
|
|
}
|
|
|
|
|
|
isAnimationPlaying() {
|
|
|
-
|
|
|
let target = this.props.entity;
|
|
|
- if (this.props.entity instanceof TargetedAnimation){
|
|
|
+ if (this.props.entity instanceof TargetedAnimation) {
|
|
|
target = this.props.entity.target;
|
|
|
}
|
|
|
|
|
|
+ return this.props.scene.getAllAnimatablesByTarget(target).length > 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ playPause(direction: number) {
|
|
|
+ if (this.state.selected) {
|
|
|
+ let target = this.props.entity;
|
|
|
+ if (this.props.entity instanceof TargetedAnimation) {
|
|
|
+ target = this.props.entity.target;
|
|
|
+ }
|
|
|
+ if (this.state.isPlaying) {
|
|
|
+ this.props.scene.stopAnimation(target);
|
|
|
+ this.setState({ isPlaying: false })
|
|
|
+ this._isPlaying = false;
|
|
|
+ this.forceUpdate();
|
|
|
+ } else {
|
|
|
+ let keys = this.state.selected.getKeys();
|
|
|
+ let firstFrame = keys[0].frame;
|
|
|
+ let LastFrame = keys[keys.length - 1].frame;
|
|
|
+ if (direction === 1){
|
|
|
+ this.props.scene.beginAnimation(target, firstFrame, LastFrame, true);
|
|
|
+ }
|
|
|
+ if (direction === -1){
|
|
|
+ this.props.scene.beginAnimation(target, LastFrame, firstFrame, true);
|
|
|
+ }
|
|
|
+ this._isPlaying = true;
|
|
|
+ this.setState({ isPlaying: true });
|
|
|
+ this.forceUpdate();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ playStopAnimation() {
|
|
|
+ let target = this.props.entity;
|
|
|
+ if (this.props.entity instanceof TargetedAnimation) {
|
|
|
+ target = this.props.entity.target;
|
|
|
+ }
|
|
|
this._isPlaying = this.props.scene.getAllAnimatablesByTarget(target).length > 0;
|
|
|
if (this._isPlaying) {
|
|
|
this.props.playOrPause && this.props.playOrPause();
|
|
|
+ return true;
|
|
|
} else {
|
|
|
this._isPlaying = false;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ analizeAnimation(animation: Animation | null) {
|
|
|
+ if (animation !== null) {
|
|
|
+ const { easingMode, easingType } = this.getAnimationProperties(animation);
|
|
|
+ let hasDefinedTangents = this.getAnimationData(animation).usesTangents;
|
|
|
+
|
|
|
+ if (easingType === undefined && easingMode === undefined && !hasDefinedTangents) {
|
|
|
+ return true;
|
|
|
+ } else {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -1124,6 +1238,20 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ updateFrameInKeyFrame(frame: number, index: number) {
|
|
|
+
|
|
|
+ if (this.state && this.state.selected) {
|
|
|
+ let animation = this.state.selected;
|
|
|
+ let keys = [...animation.getKeys()];
|
|
|
+
|
|
|
+ keys[index].frame = frame;
|
|
|
+
|
|
|
+ animation.setKeys(keys);
|
|
|
+
|
|
|
+ this.selectAnimation(animation);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
render() {
|
|
|
return (
|
|
|
<div id="animation-curve-editor">
|
|
@@ -1145,12 +1273,14 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
|
|
|
removeKeyframe={() => this.removeKeyframeClick()}
|
|
|
brokenMode={this.state.isBrokenMode}
|
|
|
brokeTangents={() => this.setBrokenMode()}
|
|
|
+ lerpMode={this.state.lerpMode}
|
|
|
+ setLerpMode={() => this.setLerpMode()}
|
|
|
flatTangent={() => this.setFlatTangent()} />
|
|
|
|
|
|
<div className="content">
|
|
|
<div className="row">
|
|
|
<div className="animation-list">
|
|
|
- <div style={{display: this._isTargetedAnimation ? "none" : "block"}}>
|
|
|
+ <div style={{ display: this._isTargetedAnimation ? "none" : "block" }}>
|
|
|
<div className="label-input">
|
|
|
<label>Animation Name</label>
|
|
|
<input type="text" value={this.state.animationName} onChange={(e) => this.handleNameChange(e)}></input>
|
|
@@ -1178,13 +1308,13 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
|
|
|
<h2>{this._entityName}</h2>
|
|
|
<ul>
|
|
|
{
|
|
|
-
|
|
|
- this.props.entity instanceof TargetedAnimation ? this.setListItem(this.props.entity.animation, 0) :
|
|
|
- this.props.entity.animations && this.props.entity.animations.map((animation, i) => {
|
|
|
|
|
|
- return this.setListItem(animation, i);
|
|
|
+ this.props.entity instanceof TargetedAnimation ? this.setListItem(this.props.entity.animation, 0) :
|
|
|
+ this.props.entity.animations && this.props.entity.animations.map((animation, i) => {
|
|
|
+
|
|
|
+ return this.setListItem(animation, i);
|
|
|
|
|
|
- })}
|
|
|
+ })}
|
|
|
|
|
|
</ul>
|
|
|
</div>
|
|
@@ -1234,7 +1364,7 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
|
|
|
</div>
|
|
|
</div>
|
|
|
<div className="row">
|
|
|
- <Timeline currentFrame={this.state.currentFrame} onCurrentFrameChange={(frame: number) => this.changeCurrentFrame(frame)} keyframes={this.state.selected && this.state.selected.getKeys()} selected={this.state.selected && this.state.selected.getKeys()[0]}></Timeline>
|
|
|
+ <Timeline currentFrame={this.state.currentFrame} playPause={(direction: number) => this.playPause(direction)} isPlaying={this.state.isPlaying} dragKeyframe={(frame: number, index: number) => this.updateFrameInKeyFrame(frame, index)} onCurrentFrameChange={(frame: number) => this.changeCurrentFrame(frame)} keyframes={this.state.selected && this.state.selected.getKeys()} selected={this.state.selected && this.state.selected.getKeys()[0]}></Timeline>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|