David Catuhe 6 年之前
父节点
当前提交
b138c13de7

+ 12 - 0
inspector/src/components/actionTabs/tabs/toolsTabComponent.tsx

@@ -116,6 +116,14 @@ export class ToolsTabComponent extends PaneComponent {
             });
     }
 
+    resetReplay() {
+        this.props.globalState.recorder.reset();
+    }
+
+    exportReplay() {
+        this.props.globalState.recorder.export();
+    }
+
     render() {
         const scene = this.props.scene;
 
@@ -129,6 +137,10 @@ export class ToolsTabComponent extends PaneComponent {
                     <ButtonLineComponent label="Screenshot" onClick={() => this.captureScreenshot()} />
                     <ButtonLineComponent label={this.state.tag} onClick={() => this.recordVideo()} />
                 </LineContainerComponent>
+                <LineContainerComponent globalState={this.props.globalState} title="REPLAY">
+                    <ButtonLineComponent label="Generate replay code" onClick={() => this.exportReplay()} />
+                    <ButtonLineComponent label="Reset" onClick={() => this.resetReplay()} />
+                </LineContainerComponent>
                 <LineContainerComponent globalState={this.props.globalState} title="SCENE EXPORT">
                     <ButtonLineComponent label="Export to GLB" onClick={() => this.exportGLTF()} />
                     <ButtonLineComponent label="Export to Babylon" onClick={() => this.exportBabylon()} />

+ 11 - 0
inspector/src/components/globalState.ts

@@ -8,6 +8,7 @@ import { Scene } from "babylonjs/scene";
 import { Light } from "babylonjs/Lights/light";
 import { LightGizmo } from "babylonjs/Gizmos/lightGizmo";
 import { PropertyChangedEvent } from "./propertyChangedEvent";
+import { ReplayRecorder } from './replayRecorder';
 
 export class GlobalState {
     public onSelectionChangedObservable: Observable<any>;
@@ -26,6 +27,16 @@ export class GlobalState {
     public blockMutationUpdates = false;
     public selectedLineContainerTitle = "";
 
+    public recorder = new ReplayRecorder();
+
+    public init(propertyChangedObservable: Observable<PropertyChangedEvent>) {
+        this.onPropertyChangedObservable = propertyChangedObservable;
+
+        propertyChangedObservable.add(event => {
+            this.recorder.record(event);
+        })
+    }
+
     public prepareGLTFPlugin(loader: GLTFFileLoader) {
         var loaderState = this.glTFLoaderDefaults;
         if (loaderState !== undefined) {

+ 71 - 0
inspector/src/components/replayRecorder.ts

@@ -0,0 +1,71 @@
+import { PropertyChangedEvent } from './propertyChangedEvent';
+import { Tools } from 'babylonjs/Misc/tools';
+
+export class ReplayRecorder {
+    private _recordedCodeLines: string[];
+    private _previousObject: any;
+    private _previousProperty: string;
+
+    public reset() {
+        this._recordedCodeLines = [];
+        this._previousObject = null;
+        this._previousProperty = "";
+    }
+
+    public record(event: PropertyChangedEvent) {
+        if (!this._recordedCodeLines) {
+            this._recordedCodeLines = [];
+        }
+
+        if (this._previousObject === event.object && this._previousProperty === event.property) {
+            this._recordedCodeLines.pop();
+        }
+
+        let value = event.value;
+
+        if (value.w) { // Quaternion
+            value = `new BABYLON.Quaternion(${value.x}, ${value.y}, ${value.z}, ${value.w})`;
+        } else if (value.z) { // Vector3
+            value = `new BABYLON.Vector3(${value.x}, ${value.y}, ${value.z})`;
+        } else if (value.y) { // Vector2
+            value = `new BABYLON.Vector2(${value.x}, ${value.y})`;
+        } else if (value.a) { // Color4
+            value = `new BABYLON.Color4(${value.r}, ${value.g}, ${value.b}, ${value.a})`;
+        } else if (value.b) { // Color3
+            value = `new BABYLON.Color3(${value.r}, ${value.g}, ${value.b})`;
+        }
+
+        let target = event.object.getClassName().toLowerCase();
+
+        if (event.object.uniqueId) {
+            if (target.indexOf("camera")) {
+                target = `scene.getCameraByUniqueID(${event.object.uniqueId})`;
+            } else if (target.indexOf("mesh")) {
+                target = `scene.getMeshByUniqueID(${event.object.uniqueId})`;
+            } else if (target.indexOf("light")) {
+                target = `scene.getLightByUniqueID(${event.object.uniqueId})`;
+            } else if (target === "transformnode") {
+                target = `scene.getTransformNodeByUniqueID(${event.object.uniqueId})`;
+            } else if (target === "skeleton") {
+                target = `scene.getSkeletonByUniqueId(${event.object.uniqueId})`;
+            } else if (target.indexOf("material")) {
+                target = `scene.getMaterialByUniqueID(${event.object.uniqueId})`;
+            }
+        }
+
+        this._recordedCodeLines.push(`${target}.${event.property} = ${value};`);
+
+        this._previousObject = event.object;
+        this._previousProperty = event.property;
+    }
+
+    public export() {
+        let content = "// Code generated by babylon.js Inspector\r\n// Please keep in mind to define the 'scene' variable before using that code\r\n\r\n";
+
+        if (this._recordedCodeLines) {
+            content += this._recordedCodeLines.join("\r\n");
+        }
+
+        Tools.Download(new Blob([content]), "pseudo-code.txt");
+    }
+}

+ 1 - 1
inspector/src/inspector.ts

@@ -323,7 +323,7 @@ export class Inspector {
 
         // Prepare state
         if (!this._GlobalState.onPropertyChangedObservable) {
-            this._GlobalState.onPropertyChangedObservable = this.OnPropertyChangedObservable;
+            this._GlobalState.init(this.OnPropertyChangedObservable);
         }
         if (!this._GlobalState.onSelectionChangedObservable) {
             this._GlobalState.onSelectionChangedObservable = this.OnSelectionChangeObservable;

+ 9 - 0
src/Bones/skeleton.ts

@@ -58,6 +58,7 @@ export class Skeleton implements IAnimatable {
     private _lastAbsoluteTransformsUpdateId = -1;
 
     private _canUseTextureForBones = false;
+    private _uniqueId = 0;
 
     /** @hidden */
     public _numBonesWithLinkedTransformNode = 0;
@@ -118,6 +119,13 @@ export class Skeleton implements IAnimatable {
     }
 
     /**
+     * Gets the unique ID of this skeleton
+     */
+    public get uniqueId(): number {
+        return this._uniqueId;
+    }
+
+    /**
      * Creates a new skeleton
      * @param name defines the skeleton name
      * @param id defines the skeleton Id
@@ -131,6 +139,7 @@ export class Skeleton implements IAnimatable {
         this.bones = [];
 
         this._scene = scene || EngineStore.LastCreatedScene;
+        this._uniqueId = this._scene.getUniqueId();
 
         this._scene.addSkeleton(this);
 

+ 45 - 0
src/scene.ts

@@ -3092,6 +3092,21 @@ export class Scene extends AbstractScene implements IAnimatable {
     }
 
     /**
+     * Get a material using its unique id
+     * @param uniqueId defines the material's unique id
+     * @return the material or null if none found.
+     */
+    public getMaterialByUniqueID(uniqueId: number): Nullable<Material> {
+        for (var index = 0; index < this.materials.length; index++) {
+            if (this.materials[index].uniqueId === uniqueId) {
+                return this.materials[index];
+            }
+        }
+
+        return null;
+    }
+
+    /**
      * get a material using its id
      * @param id defines the material's ID
      * @return the material or null if none found.
@@ -3398,6 +3413,21 @@ export class Scene extends AbstractScene implements IAnimatable {
     }
 
     /**
+     * Gets a transform node with its auto-generated unique id
+     * @param uniqueId efines the unique id to search for
+     * @return the found transform node or null if not found at all.
+     */
+    public getTransformNodeByUniqueID(uniqueId: number): Nullable<TransformNode> {
+        for (var index = 0; index < this.transformNodes.length; index++) {
+            if (this.transformNodes[index].uniqueId === uniqueId) {
+                return this.transformNodes[index];
+            }
+        }
+
+        return null;
+    }
+
+    /**
      * Gets a list of transform nodes using their id
      * @param id defines the id to search for
      * @returns a list of transform nodes
@@ -3586,6 +3616,21 @@ export class Scene extends AbstractScene implements IAnimatable {
     }
 
     /**
+     * Gets a skeleton using a given auto generated unique id
+     * @param  uniqueId defines the unique id to search for
+     * @return the found skeleton or null if not found at all.
+     */
+    public getSkeletonByUniqueId(uniqueId: number): Nullable<Skeleton> {
+        for (var index = 0; index < this.skeletons.length; index++) {
+            if (this.skeletons[index].uniqueId === uniqueId) {
+                return this.skeletons[index];
+            }
+        }
+
+        return null;
+    }
+
+    /**
      * Gets a skeleton using a given id (if many are found, this function will pick the first one)
      * @param id defines the id to search for
      * @return the found skeleton or null if not found at all.