Ver código fonte

Keyframe Dragging

Alejandro Toledo 5 anos atrás
pai
commit
25bee6e9ad

+ 40 - 4
inspector/src/components/actionTabs/tabs/propertyGrids/animations/anchorSvgPoint.tsx

@@ -8,18 +8,54 @@ interface IAnchorSvgPointProps {
 }
 
 
-export class AnchorSvgPoint extends React.Component<IAnchorSvgPointProps>{ 
+export class AnchorSvgPoint extends React.Component<IAnchorSvgPointProps, { position: Vector2, active: boolean, offset: Vector2 } >{ 
     constructor(props: IAnchorSvgPointProps) {
         super(props);
+
+        this.state = { offset: new Vector2(this.props.anchor.x,this.props.anchor.y), position: new Vector2(this.props.anchor.x,this.props.anchor.y), active: false }
     }
+
+    handlePointerDown(e: React.PointerEvent<SVGSVGElement>) {
+        const el = e.target as SVGSVGElement;
+        const bbox = el.getBoundingClientRect();
+        const x = e.clientX - bbox.left;
+        const y = e.clientY - bbox.top;
+        el.setPointerCapture(e.pointerId);
+        this.setState({ active: true, position: new Vector2(x,y), offset: new Vector2(x, y) });
+      };
+
+    handlePointerMove(e: React.PointerEvent<SVGSVGElement>) {
+        const el = e.target as SVGSVGElement;
+        const bbox = el.getBoundingClientRect();
+        const x = e.clientX - bbox.left;
+        const y = e.clientY - bbox.top;
+        if (this.state.active) {
+
+            
+            this.setState({ 
+                active: true, 
+                position: new Vector2(this.state.position.x - (this.state.offset.x - x), this.state.position.y - (this.state.offset.y - y))
+            })
+
+        }
+      };
+
+    handlePointerUp(e: React.PointerEvent<SVGSVGElement>) {
+        this.setState({ active: false})
+      };
+
     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.anchor.x} y={this.props.anchor.y} style={{overflow:'visible'}}       
+            onPointerDown={(e) => this.handlePointerDown(e)}
+            onPointerUp={(e) => this.handlePointerUp(e)}
+            onPointerMove={(e) => this.handlePointerMove(e)}>
+                <circle cx="0" cy="0"  r="0.75" stroke="none" strokeWidth="0" fill={this.state.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" />
         </>
         )
     }
-} 
+} 
+

+ 169 - 6
inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationCurveEditorComponent.tsx

@@ -1,6 +1,6 @@
 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';
@@ -15,15 +15,145 @@ interface IAnimationCurveEditorComponentProps {
     title: string;
     animations: Animation[];
     entityName: string;
+    entity: Animatable;
 }
 
-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, selectedProperty:string, isOpen: boolean, selected: Animation, currentPathData: string | undefined, anchorPoints: { point: Vector2, anchor: Vector2 }[] | undefined, keyframes: Vector2[] | undefined }> {
 
     private _anchorPoints: { point: Vector2, anchor: Vector2 }[] = [];
+    private _newAnimations: Animation[] = [];
     private _keyframes: Vector2[] = [];
     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]), anchorPoints: this._anchorPoints, keyframes: this._keyframes, selectedProperty: 'position.x', animationName: "" }
+        
+    }
+
+    handlePropertyChange(event: React.ChangeEvent<HTMLSelectElement>){
+        event.preventDefault();
+        this.setState({selectedProperty: event.target.value});
+    }
+
+    handleNameChange(event: React.ChangeEvent<HTMLInputElement>){
+        //let value = (event.target as HTMLInputElement).value;
+        event.preventDefault();
+        this.setState({animationName: event.target.value});
+    }
+
+    addAnimation(event: React.MouseEvent<HTMLDivElement>){
+        event.preventDefault();
+        if (this.state.animationName != ""){
+            let animation = new Animation(this.state.animationName, this.state.selectedProperty, 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
+
+            var keys = []; 
+// //At the animation key 0, the value of scaling is "1"
+  keys.push({
+    frame: 0,
+    value: 1
+  });
+//   //At the animation key 20, the value of scaling is "0.2"
+//   keys.push({
+//     frame: 20,
+//     value: 0.2
+//   });
+//   //At the animation key 100, the value of scaling is "1"
+//   keys.push({
+//     frame: 100,
+//     value: 1
+//   });
+
+animation.setKeys(keys);
+
+var bezierEase = new BABYLON.CircleEase();
+//var bezierEase = new BABYLON.BezierCurveEase(0.55,0,1,0.45) as unknown;
+bezierEase.setEasingMode(BABYLON.EasingFunction.EASINGMODE_EASEINOUT);
+animation.setEasingFunction((bezierEase as unknown) as EasingFunction);
+
+            this._newAnimations.push(animation);
+            this.setState({animations: this._newAnimations, animationName: ""});
+        }
+
+    
+    }
+
+    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 keyframes = this.state.keyframes?.map((k, i) => {
+            if (i === index){
+                k.x = keyframe.x;
+                k.y = keyframe.y;
+            }
+
+            var height = 100;
+            var middle = (height / 2);
+
+            var keyValue;
+
+            if (k.y < middle){
+                keyValue = 1 + ((100/k.y) * .1)
+            }
+
+            if (k.y > middle){
+                keyValue = 1 - ((100/k.y) * .1)
+            }
+
+
+            keys.push({frame: k.x, value: keyValue})
+            return k;
+        });
+        anim.setKeys(keys);
+
+        this.setState({ keyframes: keyframes})
+
+
+
     }
 
     getAnimationProperties(animation: Animation) {
@@ -206,16 +336,49 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
                     </div>
                 </div>
                 <div className="content">
+                  
                     <div className="animation-list">
+
+                    <div>
+                        <select value={this.state.selectedProperty} onChange={(e) => this.handlePropertyChange(e)}>
+                            <option value="position.x">Position X</option>
+                            <option value="position.y">Position Y</option>
+                            <option value="position.z">Position Z</option>
+                        </select>
+
+                        <input type="text" value={this.state.animationName} onChange={(e) => this.handleNameChange(e)}></input>
+
+                        <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">
+                    <div className="sample-chart" style={{width:500, height:500}}>
+
                         <svg className="linear" viewBox="0 0 100 100" preserveAspectRatio="none">
+                        
+                           <KeyframeSvgPoint key={0} point={new Vector2(50,50)} onUpdate={(keyframe: Vector2, index: number) => {}} index={0} />
+
+                        </svg>
+                    </div>
+                    <div className="graph-chart">
+                        <svg className="linear" viewBox="0 0 100 100" preserveAspectRatio="none" onMouseDown={(e) => this.addKeyFrame(e)}>
                              {/* 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>
@@ -268,7 +431,7 @@ export class AnimationCurveEditorComponent extends React.Component<IAnimationCur
                             )}
 
                             {this.state.keyframes?.map((keyframe, i) =>
-                                <KeyframeSvgPoint key={i} point={keyframe} />
+                                <KeyframeSvgPoint key={i} point={keyframe} onUpdate={(keyframe: Vector2, index: number) => this.updateKeyframe(keyframe, index)} index={i} />
                             )}
 
                         </svg>

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

@@ -204,7 +204,7 @@ 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" entity={animatableAsAny} entityName={animatableAsAny.id} close={(event) => this.onCloseAnimationCurveEditor(event.view)} animations={animations}/>
                                 </PopupComponent>
                             }
                         </LineContainerComponent>

+ 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;

+ 108 - 3
inspector/src/components/actionTabs/tabs/propertyGrids/animations/keyframeSvgPoint.tsx

@@ -3,17 +3,122 @@ import { Vector2 } from 'babylonjs/Maths/math.vector';
 
 interface IKeyframeSvgPointProps {
     point: Vector2;
+    index: number;
+    onUpdate: (keyframe: Vector2, index: number) => void
 }
 
-export class KeyframeSvgPoint extends React.Component<IKeyframeSvgPointProps>{ 
+export class KeyframeSvgPoint extends React.Component<IKeyframeSvgPointProps,{ active: boolean, position: Vector2}>{ 
+    
+    private _active = false;
+    private _offset: Vector2;
+    private _draggable: React.RefObject<SVGSVGElement>;
+    private _local: Vector2;
+    
     constructor(props: IKeyframeSvgPointProps) {
         super(props);
+        this._draggable = React.createRef();  
+        this.state = { position: this.props.point, active: this._active }
+        this._local = new Vector2(0,0);
     }
+
+    dragStart(e: React.TouchEvent<SVGSVGElement>) : void;
+    dragStart(e: React.MouseEvent<SVGSVGElement, MouseEvent>) : void;
+    dragStart(e: any): void {
+        e.preventDefault();    
+          if (e.currentTarget === this._draggable.current) {
+            this._active = true;
+          }
+          
+    }
+
+    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);//.subtract(this._offset)
+            this._local = this.getMousePosition(e);
+            //console.log(this._local.x, this._local.y)
+          //this.setTranslate(coord.x, coord.y, this._draggable);
+          //this._local = coord;
+          this.setState({ position: coord });
+
+        }
+    }
+
+    dragEnd(e: React.TouchEvent<SVGSVGElement> | React.MouseEvent<SVGSVGElement, MouseEvent>) {
+        this._active = false;  
+    }
+
+    setTranslate(xPos: number, yPos: number, el: React.RefObject<SVGSVGElement>) {
+        if (el.current){
+            el.current.style.transform = "translate(" + xPos + "px, " + yPos + "px)";
+        }   
+    }
+
+    getMousePosition(e: React.TouchEvent<SVGSVGElement>) : Vector2;
+    getMousePosition(e: React.MouseEvent<SVGSVGElement, MouseEvent>) : Vector2;
+    getMousePosition(e: any): Vector2 {
+
+        if (e.touches) { e = e.touches[0]; }
+
+        var svg = this._draggable.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 new Vector2(e.clientX, e.clientY);
+        // }
+
+        var pt = svg.createSVGPoint();
+
+        pt.x = e.clientX;
+        pt.y = e.clientY;
+
+        var inverse = svg.getScreenCTM()?.inverse();
+
+        var cursorpt =  pt.matrixTransform(inverse);
+        
+
+        return new Vector2(cursorpt.x, cursorpt.y);
+
+        // var pt = svg.createSVGPoint();
+
+        // pt.x = e.clientX;
+        // pt.y = e.clientY;
+
+        // var inverse = svg.getScreenCTM()?.inverse();
+
+        // var cursorpt =  pt.matrixTransform(inverse);
+
+     
+       //onMouseLeave = {(e) => this.dragEnd(e)}
+        
+        //return new Vector2(cursorpt.x, cursorpt.y);
+      }
+
+
+
+
+
+
     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 className="draggable" ref={this._draggable} x={this.state.position.x} y={this.state.position.y} style={{overflow:'visible'}}
+                       onTouchMove = {(e) => this.drag(e)} 
+                       onTouchStart = {(e) => this.dragStart(e)}
+                       onTouchEnd  ={(e) => this.dragEnd(e)}
+                       onMouseMove={(e) => this.drag(e)} 
+                       onMouseDown ={(e) => this.dragStart(e)}
+                       onMouseUp  ={(e) => this.dragEnd(e)}
+                       >
+                <circle cx="0" cy="0"  r="2" stroke="none" strokeWidth="0" fill="red" />
+    
+
             </svg>
         </>
         )

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

@@ -0,0 +1,116 @@
+import * as React from "react";
+import { Vector2 } from 'babylonjs/Maths/math.vector';
+
+interface ISvgDraggableAreaProps {
+    points: Vector2[];
+}
+
+export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps,{ points: Vector2[]}>{ 
+
+    private _active: Vector2;
+
+    constructor(props: ISvgDraggableAreaProps) {
+        super(props);
+        this.state = { points: this.props.points }
+    }
+
+   
+    dragStart(e: React.TouchEvent<SVGSVGElement>, point: Vector2) : void;
+    dragStart(e: React.MouseEvent<SVGSVGElement, MouseEvent>, point: Vector2) : void;
+    dragStart(e: any, point: Vector2): void {
+        e.preventDefault();    
+            this._active = ;
+          
+    }
+
+    drag(e: React.TouchEvent<SVGSVGElement>, point: Vector2) : void;
+    drag(e: React.MouseEvent<SVGSVGElement, MouseEvent>, point: Vector2) : void;
+    drag(e: any, point: Vector2): void {
+        if (this._active) {
+        
+          e.preventDefault();
+
+          var coord = this.getMousePosition(e);//.subtract(this._offset)
+            this._local = this.getMousePosition(e);
+            //console.log(this._local.x, this._local.y)
+          //this.setTranslate(coord.x, coord.y, this._draggable);
+          //this._local = coord;
+          this.setState({ position: coord });
+
+        }
+    }
+
+    dragEnd(e: React.TouchEvent<SVGSVGElement> | React.MouseEvent<SVGSVGElement, MouseEvent>, point: Vector2) {
+        this._active = false;  
+    }
+
+
+
+    getMousePosition(e: React.TouchEvent<SVGSVGElement>, point: Vector2) : Vector2;
+    getMousePosition(e: React.MouseEvent<SVGSVGElement, MouseEvent>, point: Vector2) : Vector2;
+    getMousePosition(e: any, point: Vector2): Vector2 {
+
+        if (e.touches) { e = e.touches[0]; }
+
+        var svg = this._draggable.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 new Vector2(e.clientX, e.clientY);
+        // }
+
+        var pt = svg.createSVGPoint();
+
+        pt.x = e.clientX;
+        pt.y = e.clientY;
+
+        var inverse = svg.getScreenCTM()?.inverse();
+
+        var cursorpt =  pt.matrixTransform(inverse);
+        
+
+        return new Vector2(cursorpt.x, cursorpt.y);
+
+     
+      }
+
+
+
+    render() {
+        return (
+        <>
+            <svg className="linear" viewBox="0 0 100 100" preserveAspectRatio="none">
+
+            { this.state.points.map((point, i) => {
+            <svg key={i} className="draggable" x={point.x} y={point.y} style={{overflow:'visible'}}
+            onTouchMove = {(e, point) => this.drag(e, point)} 
+            onTouchStart = {(e,point) => this.dragStart(e, point)}
+            onTouchEnd  ={(e, point) => this.dragEnd(e, point)}
+            onMouseMove={(e, point) => this.drag(e, point)} 
+            onMouseDown ={(e, point) => this.dragStart(e, point)}
+            onMouseUp  ={(e, point) => this.dragEnd(e, point)}
+            onMouseLeave = {(e, point) => this.dragEnd(e, point)}
+            >
+            <circle cx="0" cy="0"  r="2" stroke="none" strokeWidth="0" fill="red" />
+
+
+            </svg>
+
+
+
+            })}
+                        
+      
+        </svg>
+        </>)
+    }
+
+
+}
+
+
+
+ 
+