浏览代码

Merge pull request #8103 from toledoal/adding-keys-curves

Add Sample Animation & Keyframe Dragging
mergify[bot] 5 年之前
父节点
当前提交
fd546ba281

+ 1 - 0
dist/preview release/what's new.md

@@ -44,6 +44,7 @@
 - Popup Window available (To be used in Curve Editor) ([pixelspace](https://github.com/devpixelspace))
 - Curve Editor to view current entity's animations (Read only) ([pixelspace](https://github.com/devpixelspace))
 - Add support to update inspector when switching to new scene ([belfortk](https://github.com/belfortk))
+- Drag keyframes in Curve Editor ([pixelspace](https://github.com/devpixelspace))
 
 ### Cameras
 

+ 10 - 5
inspector/src/components/actionTabs/tabs/propertyGrids/animations/anchorSvgPoint.tsx

@@ -3,8 +3,11 @@ import * as React from "react";
 import { Vector2 } from 'babylonjs/Maths/math.vector';
 
 interface IAnchorSvgPointProps {
-   point: Vector2;
+   control: Vector2;
    anchor: Vector2;
+   active: boolean;
+   type: string;
+   index: string;
 }
 
 
@@ -12,14 +15,16 @@ export class AnchorSvgPoint extends React.Component<IAnchorSvgPointProps>{
     constructor(props: IAnchorSvgPointProps) {
         super(props);
     }
+    
     render() {
         return (
         <>
-            <svg x={this.props.anchor.x} y={this.props.anchor.y} style={{overflow:'visible'}}>
-                <circle cx="0" cy="0"  r="0.75" stroke="none" strokeWidth="0" fill="blue" />
+            <svg x={this.props.control.x} y={this.props.control.y} style={{overflow:'visible'}}>
+                <circle type={this.props.type} data-id={this.props.index} className="draggable control-point" cx="0" cy="0"  r="2" stroke="none" strokeWidth="0" fill={this.props.active ? "blue" : "black"}   />
             </svg>
-            <line x1={this.props.point.x} y1={this.props.point.y} x2={this.props.anchor.x} y2={this.props.anchor.y} stroke="green" strokeWidth="0.75" />
+            <line x1={this.props.anchor.x} y1={this.props.anchor.y} x2={this.props.control.x} y2={this.props.control.y} stroke="green" strokeWidth="0.75" />
         </>
         )
     }
-} 
+} 
+

+ 319 - 83
inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationCurveEditorComponent.tsx

@@ -1,35 +1,159 @@
 import * as React from "react";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faTimes } from "@fortawesome/free-solid-svg-icons";
+import { faTimes, faPlusCircle } from "@fortawesome/free-solid-svg-icons";
 import { Animation } from 'babylonjs/Animations/animation';
 import { Vector2 } from 'babylonjs/Maths/math.vector';
-import { EasingFunction } from 'babylonjs/Animations/easing';
+import { EasingFunction, BezierCurveEase } from 'babylonjs/Animations/easing';
 import { IAnimationKey } from 'babylonjs/Animations/animationKey';
-import { AnchorSvgPoint } from './anchorSvgPoint';
-import { KeyframeSvgPoint } from './keyframeSvgPoint';
+import { IKeyframeSvgPoint } from './keyframeSvgPoint';
+import { SvgDraggableArea } from './svgDraggableArea';
+import { Scene } from "babylonjs/scene";
+import { IAnimatable } from 'babylonjs/Animations/animatable.interface';
 
 require("./curveEditor.scss");
 
 interface IAnimationCurveEditorComponentProps {
     close: (event: any) => void;
+    playOrPause: () => void;
     title: string;
     animations: Animation[];
     entityName: string;
+    scene: Scene;
+    entity: IAnimatable;
 }
 
-export class AnimationCurveEditorComponent extends React.Component<IAnimationCurveEditorComponentProps, { isOpen: boolean, selected: Animation, currentPathData: string | undefined, anchorPoints: { point: Vector2, anchor: Vector2 }[] | null, keyframes: Vector2[] | null }> {
+export class AnimationCurveEditorComponent extends React.Component<IAnimationCurveEditorComponentProps, { animations: Animation[], animationName: string, animationTargetProperty: string, isOpen: boolean, selected: Animation, currentPathData: string | undefined, svgKeyframes: IKeyframeSvgPoint[] | undefined }> {
 
-    private _anchorPoints: { point: Vector2, anchor: Vector2 }[] = [];
-    private _keyframes: Vector2[] = [];
+    readonly _heightScale: number = 100;
+    private _newAnimations: Animation[] = [];
+    private _svgKeyframes: IKeyframeSvgPoint[] = [];
+    private _frames: Vector2[] = [];
+    private _isPlaying: boolean = false;
     constructor(props: IAnimationCurveEditorComponentProps) {
         super(props);
-        this.state = { selected: this.props.animations[0], isOpen: true, currentPathData: this.getPathData(this.props.animations[0]), anchorPoints: this._anchorPoints, keyframes: this._keyframes }
+        this.state = { animations: this._newAnimations, selected: this.props.animations[0], isOpen: true, currentPathData: this.getPathData(this.props.animations[0]), svgKeyframes: this._svgKeyframes, animationTargetProperty: 'position.x', animationName: "" }
+
+    }
+
+    handleNameChange(event: React.ChangeEvent<HTMLInputElement>) {
+        event.preventDefault();
+        this.setState({ animationName: event.target.value });
+    }
+
+    handlePropertyChange(event: React.ChangeEvent<HTMLInputElement>) {
+        event.preventDefault();
+        this.setState({ animationTargetProperty: event.target.value });
+    }
+
+    addAnimation(event: React.MouseEvent<HTMLDivElement>) {
+        event.preventDefault();
+        if (this.state.animationName != "" && this.state.animationTargetProperty != "") {
+            let animation = new Animation(this.state.animationName, this.state.animationTargetProperty, 30, Animation.ANIMATIONTYPE_FLOAT, Animation.ANIMATIONLOOPMODE_CYCLE);
+
+            var keys = [];
+            keys.push({
+                frame: 0,
+                value: 1
+            });
+
+            keys.push({
+                frame: 100,
+                value: 1
+            });
+
+
+            animation.setKeys(keys);
+
+            var bezierEase = new BezierCurveEase(10, 0, 10, 0);
+            bezierEase.setEasingMode(EasingFunction.EASINGMODE_EASEINOUT);
+            animation.setEasingFunction((bezierEase as unknown) as EasingFunction);
+
+            // Need to redefine/refactor not to update the prop collection
+            (this.props.entity as IAnimatable).animations?.push(animation);
+
+        }
+    }
+
+    addKeyFrame(event: React.MouseEvent<SVGSVGElement>) {
+
+        event.preventDefault();
+
+        if (event.button === 2) {
+
+            var svg = event.target as SVGSVGElement;
+
+            var pt = svg.createSVGPoint();
+
+            pt.x = event.clientX;
+            pt.y = event.clientY;
+
+            var inverse = svg.getScreenCTM()?.inverse();
+
+            var cursorpt = pt.matrixTransform(inverse);
+
+            var currentAnimation = this.state.selected;
+
+            var keys = currentAnimation.getKeys();
+
+            var height = 100;
+            var middle = (height / 2);
+
+            var keyValue;
+
+            if (cursorpt.y < middle) {
+                keyValue = 1 + ((100 / cursorpt.y) * .1)
+            }
+
+            if (cursorpt.y > middle) {
+                keyValue = 1 - ((100 / cursorpt.y) * .1)
+            }
+
+            keys.push({ frame: cursorpt.x, value: keyValue });
+
+            currentAnimation.setKeys(keys);
+
+            this.selectAnimation(currentAnimation);
+        }
+    }
+
+    updateKeyframe(keyframe: Vector2, index: number) {
+
+        let anim = this.state.selected as Animation;
+        var keys: IAnimationKey[] = [];
+
+        var svgKeyframes = this.state.svgKeyframes?.map((k, i) => {
+            if (i === index) {
+                k.keyframePoint.x = keyframe.x;
+                k.keyframePoint.y = keyframe.y;
+            }
+
+            var height = 100;
+            var middle = (height / 2);
+
+            var keyValue;
+
+            if (k.keyframePoint.y < middle) {
+                keyValue = 1 + ((100 / k.keyframePoint.y) * .1)
+            }
+
+            if (k.keyframePoint.y > middle) {
+                keyValue = 1 - ((100 / k.keyframePoint.y) * .1)
+            }
+
+
+            keys.push({ frame: k.keyframePoint.x, value: keyValue })
+            return k;
+        });
+        anim.setKeys(keys);
+
+        this.setState({ svgKeyframes: svgKeyframes })
+
     }
 
     getAnimationProperties(animation: Animation) {
         let easingType, easingMode;
         let easingFunction: EasingFunction = animation.getEasingFunction() as EasingFunction;
-        if (easingFunction === undefined){
+        if (easingFunction === undefined) {
             easingType = undefined
             easingMode = undefined;
         } else {
@@ -44,75 +168,107 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
         const { easingMode, easingType } = this.getAnimationProperties(animation);
 
         const keyframes = animation.getKeys();
+
+
         if (keyframes === undefined) {
             return "";
         }
 
         const startKey = keyframes[0];
 
-        // This will change when Graph width becomes dinamic
-        const heightScale = 100;
-
-        // This assumes the startkey is always 0... we will change this
-        let middle = (startKey.value / 2) * heightScale;
+        // This assumes the startkey is always 0... beed to change this
+        let middle = this._heightScale / 2;
 
         // START OF LINE/CURVE
-        let data: string | undefined = `M0, ${middle}`;
+        let data: string | undefined = `M${startKey.frame}, ${this._heightScale - (startKey.value * middle)}`;
 
-        if (easingType === undefined && easingMode === undefined){
-            data = this.linearInterpolation(keyframes, data, heightScale, middle);
+        if (easingType === undefined && easingMode === undefined) {
+            data = this.linearInterpolation(keyframes, data, middle);
         } else {
             let easingFunction = animation.getEasingFunction();
-            data = this.curvePath(keyframes, data, heightScale, middle, easingFunction as EasingFunction)
+
+            data = this.curvePath(keyframes, data, middle, easingFunction as EasingFunction)
         }
 
+
+
         return data;
 
     }
 
-    curvePath(keyframes: IAnimationKey[], data: string, heightScale: number, middle: number, easingFunction: EasingFunction) {
+    drawAllFrames(initialKey: IAnimationKey, endKey: IAnimationKey, easingFunction: EasingFunction) {
+
+        let i = initialKey.frame;
+
+        for (i; i < endKey.frame; i++) {
+
+            (i * 100 / endKey.frame)
+
+            let dy = easingFunction.easeInCore(i);
+            let value = this._heightScale - (dy * (this._heightScale / 2));
+            this._frames.push(new Vector2(i, value));
+
+        }
+
+    }
+
+    curvePath(keyframes: IAnimationKey[], data: string, middle: number, easingFunction: EasingFunction) {
 
         // This will get 1/4 and 3/4 of points in eased curve
         const u = .25;
         const v = .75;
 
         keyframes.forEach((key, i) => {
-            if (i !== 0) {
 
-                // Gets previous initial point of curve segment
-                var pointA =  new Vector2(0, 0);
-                if (i === 0) {
-                    pointA.x = 0
-                    pointA.y = middle;
-                } else {
-                    pointA.x = keyframes[i - 1].frame;
-                    pointA.y = heightScale - (keyframes[i - 1].value * middle)
-                }
+            // Gets previous initial point of curve segment
+            var pointA = new Vector2(0, 0);
+            if (i === 0) {
+                pointA.x = key.frame;
+                pointA.y = this._heightScale - (key.value * middle);
+
+                this.setKeyframePoint([pointA], i, keyframes.length);
+
+            } else {
+                pointA.x = keyframes[i - 1].frame;
+                pointA.y = this._heightScale - (keyframes[i - 1].value * middle)
 
                 // Gets the end point of this curve segment
-                var pointB = new Vector2(key.frame, heightScale - (key.value * middle));
+                var pointB = new Vector2(key.frame, this._heightScale - (key.value * middle));
 
                 // Get easing value of percentage to get the bezier control points below
-                let du = easingFunction.ease(u); // What to do here, when user edits the curve? Option 1: Modify the curve with the new control points as BezierEaseCurve(x,y,z,w)
-                let dv = easingFunction.ease(v); // Option 2: Create a easeInCore function and adapt it with the new control points values... needs more revision.
+                let du = easingFunction.easeInCore(u); // What to do here, when user edits the curve? Option 1: Modify the curve with the new control points as BezierEaseCurve(x,y,z,w)
+                let dv = easingFunction.easeInCore(v); // Option 2: Create a easeInCore function and adapt it with the new control points values... needs more revision.
+
+                // Direction of curve up/down
+                let yInt25 = 0;
+                if (pointB.y > pointA.y) {  // if pointB.y > pointA.y = goes down 
+                    yInt25 = ((pointB.y - pointA.y) * du) + pointA.y
+                } else if (pointB.y < pointA.y) {     // if pointB.y < pointA.y = goes up
+                    yInt25 = pointA.y - ((pointA.y - pointB.y) * du);
+                }
+
+                let yInt75 = 0;
+                if (pointB.y > pointA.y) {
+                    yInt75 = ((pointB.y - pointA.y) * dv) + pointA.y
+                } else if (pointB.y < pointA.y) {
+                    yInt75 = pointA.y - ((pointA.y - pointB.y) * dv)
+                }
 
                 // Intermediate points in curve
-                let intermediatePoint25 = new Vector2(((pointB.x - pointA.x) * u) + pointA.x,  ((pointB.y - pointA.y) * du) + middle);
-                let intermediatePoint75 = new Vector2(((pointB.x - pointA.x) * v) + pointA.x,  ((pointB.y - pointA.y) * dv) + middle);
-                
+                let intermediatePoint25 = new Vector2(((pointB.x - pointA.x) * u) + pointA.x, yInt25);
+                let intermediatePoint75 = new Vector2(((pointB.x - pointA.x) * v) + pointA.x, yInt75);
+
+
                 // Gets the four control points of bezier curve
                 let controlPoints = this.interpolateControlPoints(pointA, intermediatePoint25, u, intermediatePoint75, v, pointB);
 
-                if (controlPoints === undefined){
+                if (controlPoints === undefined) {
                     console.log("error getting bezier control points");
                 } else {
-                    this.setAnchorPoint(controlPoints[0], controlPoints[1]);
-                    this.setAnchorPoint(controlPoints[3], controlPoints[2]);
-    
-                    this.setKeyframePoint(pointA);
-                    this.setKeyframePoint(pointB);
-    
-                    data += ` C${controlPoints[1].x}, ${controlPoints[1].y} ${controlPoints[2].x}, ${controlPoints[2].y} ${pointB.x}, ${pointB.y}`
+
+                    this.setKeyframePoint(controlPoints, i, keyframes.length);
+
+                    data += ` C${controlPoints[1].x} ${controlPoints[1].y} ${controlPoints[2].x} ${controlPoints[2].y} ${controlPoints[3].x} ${controlPoints[3].y}`
 
                 }
             }
@@ -123,45 +279,97 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
 
     }
 
-    
-    linearInterpolation(keyframes: IAnimationKey[], data: string, heightScale: number, middle: number): string {
+
+
+    renderPoints(updatedSvgKeyFrame: IKeyframeSvgPoint, index: number) {
+
+        let animation = this.state.selected as Animation;
+
+        let keys = [...animation.getKeys()];
+
+        let newFrame = 0;
+        if (updatedSvgKeyFrame.keyframePoint.x !== 0) {
+            if (updatedSvgKeyFrame.keyframePoint.x > 0 && updatedSvgKeyFrame.keyframePoint.x < 1) {
+                newFrame = 1;
+            } else {
+                newFrame = Math.round(updatedSvgKeyFrame.keyframePoint.x);
+            }
+        }
+
+        keys[index].frame = newFrame; // This value comes as percentage/frame/time
+        keys[index].value = ((this._heightScale - updatedSvgKeyFrame.keyframePoint.y) / this._heightScale) * 2; // this value comes inverted svg from 0 = 100 to 100 = 0
+
+        animation.setKeys(keys);
+
+        this.selectAnimation(animation);
+
+    }
+
+
+    linearInterpolation(keyframes: IAnimationKey[], data: string, middle: number): string {
         keyframes.forEach((key, i) => {
+
+            var point = new Vector2(0, 0);
+            point.x = key.frame;
+            point.y = this._heightScale - (key.value * middle);
+            this.setKeyframePointLinear(point, i);
+
             if (i !== 0) {
-                data += ` L${key.frame} ${heightScale - (key.value * middle)}`
+                data += ` L${point.x} ${point.y}`
             }
-
         });
         return data;
     }
 
-    
-    setAnchorPoint(point: Vector2, anchor: Vector2) {
-        this._anchorPoints.push({ point, anchor });
+    setKeyframePointLinear(point: Vector2, index: number) {
+        let svgKeyframe = { keyframePoint: point, rightControlPoint: null, leftControlPoint: null, id: index.toString() }
+        this._svgKeyframes.push(svgKeyframe);
     }
 
-    setKeyframePoint(point: Vector2) {
-        this._keyframes.push(point);
+    setKeyframePoint(controlPoints: Vector2[], index: number, keyframesCount: number) {
+
+        let svgKeyframe;
+        if (index === 0) {
+            svgKeyframe = { keyframePoint: controlPoints[0], rightControlPoint: null, leftControlPoint: null, id: index.toString() }
+        } else {
+            this._svgKeyframes[index - 1].rightControlPoint = controlPoints[1];
+            svgKeyframe = { keyframePoint: controlPoints[3], rightControlPoint: null, leftControlPoint: controlPoints[2], id: index.toString() }
+        }
+
+        this._svgKeyframes.push(svgKeyframe);
+    }
+
+    isAnimationPlaying() {
+          this._isPlaying = this.props.scene.getAllAnimatablesByTarget(this.props.entity).length > 0;
+           if (this._isPlaying){
+            this.props.playOrPause();
+           } else {
+               this._isPlaying = false;
+           }
     }
 
     selectAnimation(animation: Animation) {
-        this._anchorPoints = [];
-        this._keyframes = [];
+
+        this.isAnimationPlaying();
+
+        this._svgKeyframes = [];
 
         const pathData = this.getPathData(animation);
         if (pathData === "") {
             console.log("no keyframes in this animation");
         }
-        this.setState({ selected: animation, currentPathData: pathData, anchorPoints: this._anchorPoints, keyframes: this._keyframes });
+
+        this.setState({ selected: animation, currentPathData: pathData, svgKeyframes: this._svgKeyframes });
 
     }
 
-    interpolateControlPoints(p0: Vector2, p1: Vector2, u: number, p2: Vector2, v:number, p3: Vector2 ): Vector2[] | undefined {
+    interpolateControlPoints(p0: Vector2, p1: Vector2, u: number, p2: Vector2, v: number, p3: Vector2): Vector2[] | undefined {
 
-        let a=0.0;
-        let b=0.0;
-        let c=0.0;
-        let d=0.0;
-        let det=0.0;
+        let a = 0.0;
+        let b = 0.0;
+        let c = 0.0;
+        let d = 0.0;
+        let det = 0.0;
         let q1: Vector2 = new Vector2();
         let q2: Vector2 = new Vector2();
         let controlA: Vector2 = p0;
@@ -169,28 +377,28 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
         let controlC: Vector2 = new Vector2();
         let controlD: Vector2 = p3;
 
-        if ( (u<=0.0) || (u>=1.0) || (v<=0.0) || (v>=1.0) || (u>=v) ){
+        if ((u <= 0.0) || (u >= 1.0) || (v <= 0.0) || (v >= 1.0) || (u >= v)) {
             return undefined;
         }
 
-        a = 3*(1-u)*(1-u)*u; b = 3*(1-u)*u*u;
-        c = 3*(1-v)*(1-v)*v; d = 3*(1-v)*v*v;
-        det = a*d - b*c;
+        a = 3 * (1 - u) * (1 - u) * u; b = 3 * (1 - u) * u * u;
+        c = 3 * (1 - v) * (1 - v) * v; d = 3 * (1 - v) * v * v;
+        det = a * d - b * c;
 
         if (det == 0.0) return undefined;
 
-        q1.x = p1.x - ( (1-u)*(1-u)*(1-u)*p0.x + u*u*u*p3.x );
-        q1.y = p1.y - ( (1-u)*(1-u)*(1-u)*p0.y + u*u*u*p3.y );
-        
-        q2.x = p2.x - ( (1-v)*(1-v)*(1-v)*p0.x + v*v*v*p3.x );
-        q2.y = p2.y - ( (1-v)*(1-v)*(1-v)*p0.y + v*v*v*p3.y );
+        q1.x = p1.x - ((1 - u) * (1 - u) * (1 - u) * p0.x + u * u * u * p3.x);
+        q1.y = p1.y - ((1 - u) * (1 - u) * (1 - u) * p0.y + u * u * u * p3.y);
 
+        q2.x = p2.x - ((1 - v) * (1 - v) * (1 - v) * p0.x + v * v * v * p3.x);
+        q2.y = p2.y - ((1 - v) * (1 - v) * (1 - v) * p0.y + v * v * v * p3.y);
 
-        controlB.x = (d*q1.x - b*q2.x)/det;
-        controlB.y = (d*q1.y - b*q2.y)/det;
 
-        controlC.x = ((-c)*q1.x + a*q2.x)/det;
-        controlC.y = ((-c)*q1.y + a*q2.y)/det;
+        controlB.x = (d * q1.x - b * q2.x) / det;
+        controlB.y = (d * q1.y - b * q2.y) / det;
+
+        controlC.x = ((-c) * q1.x + a * q2.x) / det;
+        controlC.y = ((-c) * q1.y + a * q2.y) / det;
 
         return [controlA, controlB, controlC, controlD];
 
@@ -206,17 +414,44 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
                     </div>
                 </div>
                 <div className="content">
+
                     <div className="animation-list">
+
+                        <div>
+                            <div>
+                                <label>Animation Name</label>
+                                <input type="text" value={this.state.animationName} onChange={(e) => this.handleNameChange(e)}></input>
+                            </div>
+                            <div>
+                                <label>Target Property</label>
+                                <input type="text" value={this.state.animationTargetProperty} onChange={(e) => this.handlePropertyChange(e)}></input>
+                            </div>
+                            <div className="add" onClick={(e) => this.addAnimation(e)}>
+                                <FontAwesomeIcon icon={faPlusCircle} />
+                            </div>
+                        </div>
+
                         <h2>{this.props.entityName}</h2>
                         <ul>
-                            {this.props.animations.map((animation, i) => {
+                            {this.props.animations && this.props.animations.map((animation, i) => {
                                 return <li className={this.state.selected.name === animation.name ? 'active' : ''} key={i} onClick={() => this.selectAnimation(animation)}>{animation.name} <strong>{animation.targetProperty}</strong></li>
                             })}
+
+                        </ul>
+
+                        <h2>New Animations</h2>
+                        <ul>
+                            {this.state.animations && this.state.animations.map((animation, i) => {
+                                return <li className={this.state.selected.name === animation.name ? 'active' : ''} key={i} onClick={() => this.selectAnimation(animation)}>{animation.name} <strong>{animation.targetProperty}</strong></li>
+                            })}
+
                         </ul>
                     </div>
                     <div className="graph-chart">
-                        <svg className="linear" viewBox="0 0 100 100" preserveAspectRatio="none">
-                             {/* Frame Labels  */}
+
+                        {this.state.svgKeyframes && <SvgDraggableArea keyframeSvgPoints={this.state.svgKeyframes} updatePosition={(updatedSvgKeyFrame: IKeyframeSvgPoint, index: number) => this.renderPoints(updatedSvgKeyFrame, index)}>
+
+                            {/* Frame Labels  */}
                             <text x="10" y="0" dx="-1em" style={{ font: 'italic 0.2em sans-serif' }}>10</text>
                             <text x="20" y="0" dx="-1em" style={{ font: 'italic 0.2em sans-serif' }}>20</text>
                             <text x="30" y="0" dx="-1em" style={{ font: 'italic 0.2em sans-serif' }}>30</text>
@@ -263,15 +498,16 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
                             { /* Single Curve -Modify this for multiple selection and view  */}
                             <path id="curve" d={this.state.currentPathData} style={{ stroke: 'red', fill: 'none', strokeWidth: '0.5' }}></path>
 
-                            {this.state.anchorPoints?.map((anchorPoint, i) =>
-                                <AnchorSvgPoint key={i} point={anchorPoint.point} anchor={anchorPoint.anchor} />
-                            )}
+                            {this._frames && this._frames.map(frame =>
+                                <svg x={frame.x} y={frame.y} style={{ overflow: 'visible' }}>
+                                    <circle cx="0" cy="0" r="2" stroke="black" strokeWidth="1" fill="white" />
+                                </svg>
 
-                            {this.state.keyframes?.map((keyframe, i) =>
-                                <KeyframeSvgPoint key={i} point={keyframe} />
                             )}
 
-                        </svg>
+                        </SvgDraggableArea>
+
+                        }
 
                         Animation name: {this.state.selected.name}
 

+ 8 - 3
inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationPropertyGridComponent.tsx

@@ -160,7 +160,6 @@ export class AnimationGridComponent extends React.Component<IAnimationGridCompon
             }
         }
 
-
         let animations = animatable.animations;
 
         return (
@@ -204,7 +203,14 @@ export class AnimationGridComponent extends React.Component<IAnimationGridCompon
                                     onOpen={(window: Window) => { window.console.log("Window opened!!") }}
                                     onClose={(window: Window) => this.onCloseAnimationCurveEditor(window)}>
 
-                                    <AnimationCurveEditorComponent title="Animations Curve Editor" entityName={animatableAsAny.id} close={(event) => this.onCloseAnimationCurveEditor(event.view)} animations={animations}/>
+                                    <AnimationCurveEditorComponent 
+                                        title="Animations Curve Editor" 
+                                        scene={this.props.scene} 
+                                        entity={animatableAsAny} 
+                                        entityName={animatableAsAny.id} 
+                                        close={(event) => this.onCloseAnimationCurveEditor(event.view)} 
+                                        playOrPause={() => this.playOrPause()}
+                                        animations={animations} />
                                 </PopupComponent>
                             }
                         </LineContainerComponent>
@@ -249,5 +255,4 @@ export class AnimationGridComponent extends React.Component<IAnimationGridCompon
             </div>
         );
     }
-
 }

+ 8 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/animations/curveEditor.scss

@@ -59,6 +59,14 @@
             }
         }
 
+        .sample-chart{
+            .linear {
+                .draggable {
+                    cursor: move;
+                }
+            }
+        }
+
         .graph-chart{
             flex: 1 1 0%;
             margin: 25% auto;

+ 21 - 6
inspector/src/components/actionTabs/tabs/propertyGrids/animations/keyframeSvgPoint.tsx

@@ -1,21 +1,36 @@
 import * as React from "react";
 import { Vector2 } from 'babylonjs/Maths/math.vector';
+import { AnchorSvgPoint } from './anchorSvgPoint';
+
+export interface IKeyframeSvgPoint {
+    keyframePoint: Vector2;
+    rightControlPoint: Vector2 | null;
+    leftControlPoint: Vector2 | null;
+    id: string;
+}
 
 interface IKeyframeSvgPointProps {
-    point: Vector2;
+    keyframePoint: Vector2;
+    leftControlPoint: Vector2 | null;
+    rightControlPoint: Vector2 | null;
+    id: string;
 }
 
 export class KeyframeSvgPoint extends React.Component<IKeyframeSvgPointProps>{ 
+ 
     constructor(props: IKeyframeSvgPointProps) {
         super(props);
     }
+
     render() {
         return (
-        <>
-            <svg x={this.props.point.x} y={this.props.point.y} style={{overflow:'visible'}}>
-                <circle cx="0" cy="0"  r="0.75" stroke="none" strokeWidth="0" fill="red" />
-            </svg>
-        </>
+            <>
+                <svg className="draggable" x={this.props.keyframePoint.x} y={this.props.keyframePoint.y} style={{overflow:'visible'}}>
+                    <circle data-id={this.props.id} className="draggable" cx="0" cy="0"  r="2" stroke="none" strokeWidth="0" fill="red" />
+                </svg>
+               { this.props.leftControlPoint && <AnchorSvgPoint type="left" index={this.props.id} control={this.props.leftControlPoint} anchor={this.props.keyframePoint} active={false}/>} 
+               { this.props.rightControlPoint &&  <AnchorSvgPoint type="right" index={this.props.id} control={this.props.rightControlPoint} anchor={this.props.keyframePoint} active={false}/>}
+            </>
         )
     }
 } 

+ 122 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/animations/svgDraggableArea.tsx

@@ -0,0 +1,122 @@
+import * as React from "react";
+import { Vector2 } from 'babylonjs/Maths/math.vector';
+import { KeyframeSvgPoint, IKeyframeSvgPoint } from './keyframeSvgPoint';
+
+interface ISvgDraggableAreaProps {
+    keyframeSvgPoints: IKeyframeSvgPoint[];
+    updatePosition: (updatedKeyframe: IKeyframeSvgPoint, index: number) => void
+}
+
+export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps>{
+
+    private _active: boolean;
+    private _isCurrentPointControl: string;
+    private _currentPointIndex: number;
+    private _draggableArea: React.RefObject<SVGSVGElement>;
+
+    constructor(props: ISvgDraggableAreaProps) {
+        super(props);
+        this._currentPointIndex = -1;
+        this._isCurrentPointControl = "";
+        this._draggableArea = React.createRef();
+    }
+
+    dragStart(e: React.TouchEvent<SVGSVGElement>): void;
+    dragStart(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
+    dragStart(e: any): void {
+        e.preventDefault();
+        if (e.target.classList.contains("draggable")) {
+            this._active = true;
+            this._currentPointIndex = parseInt(e.target.getAttribute('data-id'));
+
+            if (e.target.classList.contains("control-point")) {
+                this._isCurrentPointControl = e.target.getAttribute("type");
+            }
+        }
+    }
+
+    drag(e: React.TouchEvent<SVGSVGElement>): void;
+    drag(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
+    drag(e: any): void {
+        if (this._active) {
+
+            e.preventDefault();
+
+            var coord = this.getMousePosition(e);
+
+            if (coord !== undefined) {
+
+                var newPoints = [...this.props.keyframeSvgPoints];
+
+                if (this._isCurrentPointControl === "left") {
+                    newPoints[this._currentPointIndex].leftControlPoint = coord;
+                } else if (this._isCurrentPointControl === "right") {
+                    newPoints[this._currentPointIndex].rightControlPoint = coord;
+                } else {
+                    newPoints[this._currentPointIndex].keyframePoint = coord;
+                }
+
+                this.props.updatePosition(newPoints[this._currentPointIndex], this._currentPointIndex);
+
+            }
+        }
+    }
+
+    dragEnd(e: React.TouchEvent<SVGSVGElement>): void;
+    dragEnd(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
+    dragEnd(e: any): void {
+        e.preventDefault();
+        this._active = false;
+        this._currentPointIndex = -1;
+        this._isCurrentPointControl = "";
+    }
+
+    getMousePosition(e: React.TouchEvent<SVGSVGElement>): Vector2 | undefined;
+    getMousePosition(e: React.MouseEvent<SVGSVGElement, MouseEvent>): Vector2 | undefined;
+    getMousePosition(e: any): Vector2 | undefined {
+
+        if (e.touches) { e = e.touches[0]; }
+
+        if (this._draggableArea.current) {
+            var svg = this._draggableArea.current as SVGSVGElement;
+            var CTM = svg.getScreenCTM();
+            if (CTM) {
+                return new Vector2((e.clientX - CTM.e) / CTM.a, (e.clientY - CTM.f) / CTM.d);
+            } else {
+                return undefined;
+            }
+        } else {
+            return undefined;
+        }
+    }
+
+    render() {
+        return (
+            <>
+                <svg className="linear" style={{ border: '1px solid black' }} ref={this._draggableArea}
+
+                    onMouseMove={(e) => this.drag(e)}
+                    onTouchMove={(e) => this.drag(e)}
+                    onTouchStart={(e) => this.dragStart(e)}
+                    onTouchEnd={(e) => this.dragEnd(e)}
+
+                    onMouseDown={(e) => this.dragStart(e)}
+                    onMouseUp={(e) => this.dragEnd(e)}
+                    onMouseLeave={(e) => this.dragEnd(e)}
+                    // Add way to add new keyframe
+
+                    viewBox="0 0 100 100" preserveAspectRatio="none">
+
+                    {this.props.children}
+                    {this.props.keyframeSvgPoints.map((keyframe, i) =>
+                        <KeyframeSvgPoint key={i} id={i.toString()} keyframePoint={keyframe.keyframePoint} leftControlPoint={keyframe.leftControlPoint} rightControlPoint={keyframe.rightControlPoint} />
+                    )}
+                </svg>
+            </>)
+    }
+}
+
+
+
+
+