浏览代码

Curve Editor Single Curve

Alejandro Toledo 5 年之前
父节点
当前提交
16f3acc9ec

+ 29 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/animations/anchorPoint.tsx

@@ -0,0 +1,29 @@
+
+import * as React from "react";
+
+interface Point {
+    x: number;
+    y: number;
+}
+
+interface IAnchorPointProps {
+   point: Point;
+   anchor: Point;
+}
+
+
+export class AnchorPoint extends React.Component<IAnchorPointProps>{ 
+    constructor(props: IAnchorPointProps) {
+        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>
+            <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" />
+        </>
+        )
+    }
+} 

+ 397 - 5
inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationCurveEditorComponent.tsx

@@ -1,28 +1,420 @@
 import * as React from "react";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { faTimes } from "@fortawesome/free-solid-svg-icons";
+import { Animation } from 'babylonjs/Animations/animation';
+import { EasingFunction, BezierCurveEase } from 'babylonjs';
+import { AnchorPoint } from './anchorPoint';
+import { KeyframePoint } from './keyframePoint';
+
+require("./curveEditor.scss");
 
 interface IAnimationCurveEditorComponentProps {
     close: (event: any) => void;
     title: string;
+    animations: Animation[];
+    entityName: string;
+}
+
+interface KeyFrame {
+    frame: number;
+    value: number;
+    inTangent?: BABYLON.Vector2 | BABYLON.Vector3 | BABYLON.Quaternion;
+    outTangent?: BABYLON.Vector2 | BABYLON.Vector3 | BABYLON.Quaternion;
+}
+
+interface Point {
+    x: number;
+    y: number;
+}
+
+
+enum EASEMODE {
+    EASEIN, EASEOUT, EASEINOUT
 }
 
-export class AnimationCurveEditorComponent extends React.Component<IAnimationCurveEditorComponentProps, { isOpen: boolean }> {
 
+export class AnimationCurveEditorComponent extends React.Component<IAnimationCurveEditorComponentProps, { isOpen: boolean, selected: Animation, currentPathData: string, anchorPoints: { point: Point, anchor: Point }[] | null, keyframes: Point[] | null }> {
+
+    private _anchorPoints: { point: Point, anchor: Point }[] = [];
+    private _keyframes: Point[] = [];
     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 }
+    }
+
+    getAnimationProperties(animation: Animation) {
+
+        let easingFunction: EasingFunction = animation.getEasingFunction() as EasingFunction;
+        let easingType = easingFunction.constructor.name;
+        let easingMode = easingFunction.getEasingMode();
+
+        return { easingType, easingMode }
+
+    }
+
+    getPathData(animation: Animation) {
+
+        const { easingMode, easingType } = this.getAnimationProperties(animation);
+
+        var easingFunction = animation.getEasingFunction();
+        console.log(easingFunction);
+
+        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;
+
+        // START OF LINE/CURVE
+        let data = `M0, ${middle}`;
+
+        // This will change setting other types 
+        if (easingType === "BezierCurveEase") {
+
+            var bezier = easingFunction as BezierCurveEase
+            data = this.bezierEasePath(keyframes, data, heightScale, middle, [bezier.x1, bezier.y1], [bezier.x2, bezier.y2]);
+
+        } else {
+
+            switch (easingMode) {
+                case EASEMODE.EASEIN:
+                    data = this.easeIn(keyframes, data, heightScale, middle);
+                    break;
+                case EASEMODE.EASEOUT:
+                    data = this.easeOut(keyframes, data, heightScale, middle);
+                    break;
+                case EASEMODE.EASEINOUT:
+                    data = this.easeInOut(keyframes, data, heightScale, middle);
+                    break;
+                default: undefined
+                    data = this.linearInterpolation(keyframes, data, heightScale, middle);
+                    break;
+            }
+
+        }
+
+        return data;
+
+    }
+
+    linearInterpolation(keyframes: KeyFrame[], data: string, heightScale: number, middle: number): string {
+        keyframes.forEach((key, i) => {
+            if (i !== 0) {
+                data += ` L${key.frame} ${heightScale - (key.value * middle)}`
+            }
+
+        });
+        return data;
+    }
+
+    easeIn(keyframes: KeyFrame[], data: string, heightScale: number, middle: number): string {
+
+        // Will refactor 
+        keyframes.forEach((key, i) => {
+            if (i !== 0) {
+
+                var pointA = [0, 0];
+                if (i === 0) {
+                    pointA = [0, middle];
+                } else {
+                    pointA = [keyframes[i - 1].frame, heightScale - (keyframes[i - 1].value * middle)];
+                }
+
+                var pointB = [key.frame, heightScale - (key.value * middle)];
+
+                var anchorA = [((pointB[0] - pointA[0]) / 4) + pointA[0], pointA[1]]
+
+                var anchorB = pointB;
+
+                this.setAnchorPoint({ x: pointA[0], y: pointA[1] }, { x: anchorA[0], y: anchorA[1] });
+                this.setAnchorPoint({ x: pointB[0], y: pointB[1] }, { x: anchorB[0], y: anchorB[1] });
+
+                this.setKeyframePoint({ x: pointA[0], y: pointA[1] });
+                this.setKeyframePoint({ x: pointB[0], y: pointB[1] });
+
+                data += ` C${anchorA[0]}, ${anchorA[1]} ${anchorB[0]}, ${anchorB[1]} ${pointB[0]}, ${pointB[1]}`
+
+            }
+
+        });
+
+        return data;
+    }
+
+    easeOut(keyframes: KeyFrame[], data: string, heightScale: number, middle: number): string {
+
+        // Will refactor 
+        keyframes.forEach((key, i) => {
+            if (i !== 0) {
+
+                var pointA = [0, 0];
+                if (i === 0) {
+                    pointA = [0, middle];
+                } else {
+                    pointA = [keyframes[i - 1].frame, heightScale - (keyframes[i - 1].value * middle)];
+                }
+
+                var pointB = [key.frame, heightScale - (key.value * middle)];
+
+                var anchorA = pointA;
+
+                var anchorB = [((pointB[0] - pointA[0]) / 4) + pointA[0], pointB[1]]
+
+                this.setAnchorPoint({ x: pointA[0], y: pointA[1] }, { x: anchorA[0], y: anchorA[1] });
+                this.setAnchorPoint({ x: pointB[0], y: pointB[1] }, { x: anchorB[0], y: anchorB[1] });
+
+                this.setKeyframePoint({ x: pointA[0], y: pointA[1] });
+                this.setKeyframePoint({ x: pointB[0], y: pointB[1] });
+
+                data += ` C${anchorA[0]}, ${anchorA[1]} ${anchorB[0]}, ${anchorB[1]} ${pointB[0]}, ${pointB[1]}`
+
+            }
+
+        });
+
+        return data;
+    }
+
+    easeInOut(keyframes: KeyFrame[], data: string, heightScale: number, middle: number): string {
+
+        // Will refactor 
+        keyframes.forEach((key, i) => {
+            if (i !== 0) {
+
+                var pointA = [0, 0];
+                if (i === 0) {
+                    pointA = [0, middle];
+                } else {
+                    pointA = [keyframes[i - 1].frame, heightScale - (keyframes[i - 1].value * middle)];
+                }
+
+                var pointB = [key.frame, heightScale - (key.value * middle)];
+
+                var anchorA = [((pointB[0] - pointA[0]) * .40) + pointA[0], pointA[1]]
+
+                var anchorB = [((pointB[0] - pointA[0]) * .60) + pointA[0], pointB[1]]
+
+                this.setAnchorPoint({ x: pointA[0], y: pointA[1] }, { x: anchorA[0], y: anchorA[1] });
+                this.setAnchorPoint({ x: pointB[0], y: pointB[1] }, { x: anchorB[0], y: anchorB[1] });
+
+                this.setKeyframePoint({ x: pointA[0], y: pointA[1] });
+                this.setKeyframePoint({ x: pointB[0], y: pointB[1] });
+
+                // In redesign check c vs C for dinamic paths
+                data += ` C${anchorA[0]}, ${anchorA[1]} ${anchorB[0]}, ${anchorB[1]} ${pointB[0]}, ${pointB[1]}`
+
+            }
+
+        });
+
+        return data;
+    }
+
+
+
+
+    bezierEasePath(keyframes: KeyFrame[], data: string, heightScale: number, middle: number, bezierA: [number, number], bezierB: [number, number]) {
+
+        //BezierCurveEase(bezierA.x: Percent, bezierA: Value up/down, bezierB.x: Percent, bezierB.y: Value up/down);
+        keyframes.forEach((key, i) => {
+            if (i !== 0) {
+
+                var pointA = [0, 0];
+                if (i === 0) {
+                    pointA = [0, middle];
+                } else {
+                    pointA = [keyframes[i - 1].frame, heightScale - (keyframes[i - 1].value * middle)];
+                }
+
+                var pointB = [key.frame, heightScale - (key.value * middle)];
+
+                var anchorA = [((pointB[0] - pointA[0]) * bezierA[0]) + pointA[0], pointA[1] * bezierA[1]];
+                var anchorB = [((pointB[0] - pointA[0]) * bezierB[0]) + pointA[0], pointB[1] * bezierB[1]];
+
+                this.setAnchorPoint({ x: pointA[0], y: pointA[1] }, { x: anchorA[0], y: anchorA[1] });
+                this.setAnchorPoint({ x: pointB[0], y: pointB[1] }, { x: anchorB[0], y: anchorB[1] });
+
+                this.setKeyframePoint({ x: pointA[0], y: pointA[1] });
+                this.setKeyframePoint({ x: pointB[0], y: pointB[1] });
+
+                data += ` C${anchorA[0]}, ${anchorA[1]} ${anchorB[0]}, ${anchorB[1]} ${pointB[0]}, ${pointB[1]}`
+
+            }
+
+        });
+
+
+        return data;
+
+
+
+
+    }
+
+    setAnchorPoint(point: Point, anchor: Point) {
+        this._anchorPoints.push({ point, anchor });
+    }
+
+    setKeyframePoint(point: Point) {
+        this._keyframes.push(point);
+    }
+
+    selectAnimation(animation: Animation) {
+        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._anchorPoints = [];
+        this._keyframes = [];
     }
 
     render() {
         return (
-            <div>
+            <div id="animation-curve-editor">
                 <div className="header">
-                    <div>{this.props.title}</div>
-                    <div style={{width:48, height:48}} className="close" onClick={(event: React.MouseEvent<HTMLDivElement, MouseEvent>) => this.props.close(event)}>
+                    <div className="title">{this.props.title}</div>
+                    <div className="close" onClick={(event: React.MouseEvent<HTMLDivElement, MouseEvent>) => this.props.close(event)}>
                         <FontAwesomeIcon icon={faTimes} />
                     </div>
                 </div>
+                <div className="content">
+                    <div className="animation-list">
+                        <h2>{this.props.entityName}</h2>
+                        <ul>
+                            {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>
+                    </div>
+                    <div className="graph-chart">
+                        <svg className="linear" viewBox="0 0 100 100" preserveAspectRatio="none">
+                             {/* 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>
+                            <text x="40" y="0" dx="-1em" style={{ font: 'italic 0.2em sans-serif' }}>40</text>
+                            <text x="50" y="0" dx="-1em" style={{ font: 'italic 0.2em sans-serif' }}>50</text>
+                            <text x="60" y="0" dx="-1em" style={{ font: 'italic 0.2em sans-serif' }}>60</text>
+                            <text x="70" y="0" dx="-1em" style={{ font: 'italic 0.2em sans-serif' }}>70</text>
+                            <text x="80" y="0" dx="-1em" style={{ font: 'italic 0.2em sans-serif' }}>80</text>
+                            <text x="90" y="0" dx="-1em" style={{ font: 'italic 0.2em sans-serif' }}>90</text>
+
+                            { /* Vertical Grid  */}
+                            <line x1="10" y1="0" x2="10" y2="100" style={{ stroke: 'black', strokeWidth: '0.2px' }}></line>
+                            <line x1="20" y1="0" x2="20" y2="100" style={{ stroke: 'black', strokeWidth: '0.2px' }}></line>
+                            <line x1="30" y1="0" x2="30" y2="100" style={{ stroke: 'black', strokeWidth: '0.2px' }}></line>
+                            <line x1="40" y1="0" x2="40" y2="100" style={{ stroke: 'black', strokeWidth: '0.2px' }}></line>
+                            <line x1="50" y1="0" x2="50" y2="100" style={{ stroke: 'black', strokeWidth: '0.2px' }}></line>
+                            <line x1="60" y1="0" x2="60" y2="100" style={{ stroke: 'black', strokeWidth: '0.2px' }}></line>
+                            <line x1="70" y1="0" x2="70" y2="100" style={{ stroke: 'black', strokeWidth: '0.2px' }}></line>
+                            <line x1="80" y1="0" x2="80" y2="100" style={{ stroke: 'black', strokeWidth: '0.2px' }}></line>
+                            <line x1="90" y1="0" x2="90" y2="100" style={{ stroke: 'black', strokeWidth: '0.2px' }}></line>
+
+                            { /* Value Labels  */}
+                            <text x="0" y="10" dx="-1em" style={{ font: 'italic 0.2em sans-serif' }}>1.8</text>
+                            <text x="0" y="20" dx="-1em" style={{ font: 'italic 0.2em sans-serif' }}>1.6</text>
+                            <text x="0" y="30" dx="-1em" style={{ font: 'italic 0.2em sans-serif' }}>1.4</text>
+                            <text x="0" y="40" dx="-1em" style={{ font: 'italic 0.2em sans-serif' }}>1.2</text>
+                            <text x="0" y="50" dx="-1em" style={{ font: 'italic 0.2em sans-serif' }}>1</text>
+                            <text x="0" y="60" dx="-1em" style={{ font: 'italic 0.2em sans-serif' }}>0.8</text>
+                            <text x="0" y="70" dx="-1em" style={{ font: 'italic 0.2em sans-serif' }}>0.6</text>
+                            <text x="0" y="80" dx="-1em" style={{ font: 'italic 0.2em sans-serif' }}>0.4</text>
+                            <text x="0" y="90" dx="-1em" style={{ font: 'italic 0.2em sans-serif' }}>0.2</text>
+
+                            { /* Horizontal Grid  */}
+                            <line x1="0" y1="10" x2="100" y2="10" style={{ stroke: 'black', strokeWidth: '0.2px' }}></line>
+                            <line x1="0" y1="20" x2="100" y2="20" style={{ stroke: 'black', strokeWidth: '0.2px' }}></line>
+                            <line x1="0" y1="30" x2="100" y2="30" style={{ stroke: 'black', strokeWidth: '0.2px' }}></line>
+                            <line x1="0" y1="40" x2="100" y2="40" style={{ stroke: 'black', strokeWidth: '0.2px' }}></line>
+                            <line x1="0" y1="50" x2="100" y2="50" style={{ stroke: 'black', strokeWidth: '0.2px' }}></line>
+                            <line x1="0" y1="60" x2="100" y2="60" style={{ stroke: 'black', strokeWidth: '0.2px' }}></line>
+                            <line x1="0" y1="70" x2="100" y2="70" style={{ stroke: 'black', strokeWidth: '0.2px' }}></line>
+                            <line x1="0" y1="80" x2="100" y2="80" style={{ stroke: 'black', strokeWidth: '0.2px' }}></line>
+                            <line x1="0" y1="90" x2="100" y2="90" style={{ stroke: 'black', strokeWidth: '0.2px' }}></line>
+
+                            { /* 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) =>
+                                <AnchorPoint key={i} point={anchorPoint.point} anchor={anchorPoint.anchor} />
+                            )}
+
+                            {this.state.keyframes?.map((keyframe, i) =>
+                                <KeyframePoint key={i} point={keyframe} />
+                            )}
+
+                        </svg>
+
+                        Animation name: {this.state.selected.name}
+
+                    </div>
+                </div>
             </div>
         );
     }
-}
+}
+
+
+/// NOTES >>>>>
+
+// UPPER LIMIT
+// BABYLON.Animation.ANIMATIONLOOPMODE_RELATIVE
+// BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
+// BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT
+
+// TYPE OF CHANGE
+//     Animation.ANIMATIONTYPE_FLOAT,
+//     Animation.ANIMATIONTYPE_VECTOR2,
+//     Animation.ANIMATIONTYPE_VECTOR3,
+//     Animation.ANIMATIONTYPE_QUATERNION,
+//     Animation.ANIMATIONTYPE_MATRIX,
+//     Animation.ANIMATIONTYPE_COLOR3,
+
+// TYPES OF CHANGE THAT ALLOW SPLINES INTERPOLATIONS
+// BABYLON.Animation.ANIMATIONTYPE_VECTOR2
+// BABYLON.Animation.ANIMATIONTYPE_VECTOR3
+// BABYLON.Animation.ANIMATIONTYPE_QUATERNION
+// <i className="e"></i>
+// {
+//     frame: 0,
+//     value: BABYLON.Vector3.Zero(),
+//     outTangent: new BABYLON.Vector3(1, 0, 0)
+//   }
+// keys.push({
+//     frame: 20,
+//     inTangent: new BABYLON.Vector3(1, 0, 0),
+//     value: new BABYLON.Vector3(1, 1, 1),
+//     outTangent: new BABYLON.Vector3(-1, 0, 0)
+//  })
+
+
+// BABYLON.Animation.prototype.floatInterpolateFunction = function (startValue, endValue, gradient) {
+//     return startValue + (endValue - startValue) * gradient;
+//   };
+
+//   BABYLON.Animation.prototype.quaternionInterpolateFunction = function (startValue, endValue, gradient) {
+//     return BABYLON.Quaternion.Slerp(startValue, endValue, gradient);
+//   };
+
+//   BABYLON.Animation.prototype.vector3InterpolateFunction = function (startValue, endValue, gradient) {
+//     return BABYLON.Vector3.Lerp(startValue, endValue, gradient);
+//   };
+
+// floatInterpolateFunction
+// quaternionInterpolateFunction
+// quaternionInterpolateFunctionWithTangents
+// vector3InterpolateFunction
+// vector3InterpolateFunctionWithTangents
+// vector2InterpolateFunction
+// vector2InterpolateFunctionWithTangents
+// sizeInterpolateFunction
+// color3InterpolateFunction
+// matrixInterpolateFunction

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

@@ -200,11 +200,11 @@ export class AnimationGridComponent extends React.Component<IAnimationGridCompon
                                 this._isCurveEditorOpen && <PopupComponent
                                     id="curve-editor"
                                     title="Curve Animation Editor"
-                                    size={{ width: 800, height: 600 }}
+                                    size={{ width: 950, height: 890 }}
                                     onOpen={(window: Window) => { window.console.log("Window opened!!") }}
                                     onClose={(window: Window) => this.onCloseAnimationCurveEditor(window)}>
 
-                                    <AnimationCurveEditorComponent title="Animations Curve Editor" close={(event) => this.onCloseAnimationCurveEditor(event.view)}/>
+                                    <AnimationCurveEditorComponent title="Animations Curve Editor" entityName={animatableAsAny.id} close={(event) => this.onCloseAnimationCurveEditor(event.view)} animations={animations}/>
                                 </PopupComponent>
                             }
                         </LineContainerComponent>

文件差异内容过多而无法显示
+ 75 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/animations/curveEditor.scss


+ 26 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/animations/keyframePoint.tsx

@@ -0,0 +1,26 @@
+import * as React from "react";
+
+interface Point {
+    x: number;
+    y: number;
+}
+
+
+interface IKeyframeProps {
+    point: Point;
+}
+
+export class KeyframePoint extends React.Component<IKeyframeProps>{ 
+    constructor(props: IKeyframeProps) {
+        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>
+        </>
+        )
+    }
+} 

+ 31 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/animations/popupComponent.tsx

@@ -14,6 +14,33 @@ export class PopupComponent extends React.Component<IPopupComponentProps, { isCo
     private _container: HTMLDivElement;
     private _window: Window | null;
 
+    private _CopyStyles(sourceDoc: HTMLDocument, targetDoc: HTMLDocument) {
+        for (var index = 0; index < sourceDoc.styleSheets.length; index++) {
+            var styleSheet: any = sourceDoc.styleSheets[index];
+            try {
+                if (styleSheet.cssRules) { // for <style> elements
+                    const newStyleEl = sourceDoc.createElement('style');
+
+                    for (var cssRule of styleSheet.cssRules) {
+                        // write the text of each rule into the body of the style element
+                        newStyleEl.appendChild(sourceDoc.createTextNode(cssRule.cssText));
+                    }
+
+                    targetDoc.head!.appendChild(newStyleEl);
+                } else if (styleSheet.href) { // for <link> elements loading CSS from a URL
+                    const newLinkEl = sourceDoc.createElement('link');
+
+                    newLinkEl.rel = 'stylesheet';
+                    newLinkEl.href = styleSheet.href;
+                    targetDoc.head!.appendChild(newLinkEl);
+                }
+            } catch (e) {
+                console.log(e);
+            }
+
+        }
+    }
+
     constructor(props: IPopupComponentProps) {
         super(props);
 
@@ -52,6 +79,8 @@ export class PopupComponent extends React.Component<IPopupComponentProps, { isCo
 
         if (this._window) {
             this._window.document.title = title;
+            this._CopyStyles(window.document, this._window.document);
+            this._window.document.body.innerHTML = "";
             this._window.document.body.appendChild(this._container);
             onOpen(this._window);
             this._window.addEventListener('beforeunload', () => this._window && onClose(this._window));
@@ -81,4 +110,6 @@ export class PopupComponent extends React.Component<IPopupComponentProps, { isCo
         return ReactDOM.createPortal(this.props.children, this._container)
     }
 
+   
+
 }