瀏覽代碼

Added GIF recording

David Catuhe 5 年之前
父節點
當前提交
fe73f0c584

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

@@ -31,6 +31,8 @@
 - Handle PBR colors as colors in linear space ([Popov72](https://github.com/Popov72))
 - Allow removing textures ([Popov72](https://github.com/Popov72))
 - Edit all textures (anisotropic, clear coat, sheen, ...) for the PBR materials ([Popov72](https://github.com/Popov72))
+- Added right click options to create PBR and Standard Materials ([Deltakosh](https://github.com/deltakosh)
+- Added support for recording GIF ([Deltakosh](https://github.com/deltakosh)
 
 ### Cameras
 

+ 2 - 2
inspector/src/components/actionTabs/lines/optionsLineComponent.tsx

@@ -97,9 +97,9 @@ export class OptionsLineComponent extends React.Component<IOptionsLineComponentP
                 <div className="options">
                     <select onChange={(evt) => this.updateValue(evt.target.value)} value={this.state.value ?? ""}>
                         {
-                            this.props.options.map((option) => {
+                            this.props.options.map((option, i) => {
                                 return (
-                                    <option key={option.label} value={option.value}>{option.label}</option>
+                                    <option key={option.label + i} value={option.value}>{option.label}</option>
                                 );
                             })
                         }

+ 4 - 0
inspector/src/components/actionTabs/lines/sliderLineComponent.tsx

@@ -93,6 +93,10 @@ export class SliderLineComponent extends React.Component<ISliderLineComponentPro
     }
 
     prepareDataToRead(value: number) {
+        if (value === null) {
+            value = 0;
+        }
+
         if (this.props.useEuler) {
             return Tools.ToDegrees(value);
         }

+ 6 - 6
inspector/src/components/actionTabs/lines/textureLineComponent.tsx

@@ -194,12 +194,12 @@ export class TextureLineComponent extends React.Component<ITextureLineComponentP
                     {
                         !this.props.hideChannelSelect && texture.isCube &&
                         <div className="control3D">
-                            <button className={this.state.face === 0 ? "px command selected" : "px command"} onClick={() => this.setState({ face: 0 })}>PX</button>
-                            <button className={this.state.face === 1 ? "nx command selected" : "nx command"} onClick={() => this.setState({ face: 1 })}>NX</button>
-                            <button className={this.state.face === 2 ? "py command selected" : "py command"} onClick={() => this.setState({ face: 2 })}>PY</button>
-                            <button className={this.state.face === 3 ? "ny command selected" : "ny command"} onClick={() => this.setState({ face: 3 })}>NY</button>
-                            <button className={this.state.face === 4 ? "pz command selected" : "pz command"} onClick={() => this.setState({ face: 4 })}>PZ</button>
-                            <button className={this.state.face === 5 ? "nz command selected" : "nz command"} onClick={() => this.setState({ face: 5 })}>NZ</button>
+                            <button className={this.state.face === 0 ? "px command selected" : "px command"} onClick={() => this.setState({ face: 0 })}>+X</button>
+                            <button className={this.state.face === 1 ? "nx command selected" : "nx command"} onClick={() => this.setState({ face: 1 })}>-X</button>
+                            <button className={this.state.face === 2 ? "py command selected" : "py command"} onClick={() => this.setState({ face: 2 })}>+Y</button>
+                            <button className={this.state.face === 3 ? "ny command selected" : "ny command"} onClick={() => this.setState({ face: 3 })}>-Y</button>
+                            <button className={this.state.face === 4 ? "pz command selected" : "pz command"} onClick={() => this.setState({ face: 4 })}>+Z</button>
+                            <button className={this.state.face === 5 ? "nz command selected" : "nz command"} onClick={() => this.setState({ face: 5 })}>-Z</button>
                         </div>
                     }
                     {

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

@@ -153,9 +153,9 @@ export class AnimationGridComponent extends React.Component<IAnimationGridCompon
                     this._ranges.length > 0 &&
                     <LineContainerComponent globalState={this.props.globalState} title="ANIMATION RANGES">
                         {
-                            this._ranges.map(range => {
+                            this._ranges.map((range, i) => {
                                 return (
-                                    <ButtonLineComponent key={range.name} label={range.name}
+                                    <ButtonLineComponent key={range.name + i} label={range.name}
                                         onClick={() => {
                                             this._mainAnimatable = null;
                                             this.props.scene.beginAnimation(animatable, range.from, range.to, true)
@@ -174,9 +174,7 @@ export class AnimationGridComponent extends React.Component<IAnimationGridCompon
                             {
                                 animations.map((anim, i) => {
                                     return (
-                                        <>
-                                            <TextLineComponent key={i} label={"#" + i + " >"} value={anim.targetProperty} />
-                                        </>           
+                                        <TextLineComponent key={anim.targetProperty + i} label={"#" + i + " >"} value={anim.targetProperty} />
                                     )
                                 })
                             }

+ 3 - 1
inspector/src/components/actionTabs/tabs/propertyGrids/lights/commonShadowLightPropertyGridComponent.tsx

@@ -41,7 +41,9 @@ export class CommonShadowLightPropertyGridComponent extends React.Component<ICom
 
         scene.meshes.forEach((m) => {
             generator.addShadowCaster(m);
-            m.receiveShadows = true;
+            if (!m.isAnInstance) {
+                m.receiveShadows = true;
+            }
         });
 
         this.forceUpdate();

+ 90 - 5
inspector/src/components/actionTabs/tabs/toolsTabComponent.tsx

@@ -28,12 +28,20 @@ import { CheckBoxLineComponent } from '../lines/checkBoxLineComponent';
 import { TextLineComponent } from '../lines/textLineComponent';
 import { FileMultipleButtonLineComponent } from '../lines/fileMultipleButtonLineComponent';
 import { OptionsLineComponent } from '../lines/optionsLineComponent';
+import { MessageLineComponent } from '../lines/messageLineComponent';
+
+const GIF = require('gif.js.optimized')
 
 export class ToolsTabComponent extends PaneComponent {
     private _videoRecorder: Nullable<VideoRecorder>;
     private _screenShotSize: IScreenshotSize = { precision: 1 };
+    private _gifOptions = {width: 512, frequency: 200};
     private _useWidthHeight = false;
     private _isExporting = false;
+    private _gifWorkerBlob: Blob;
+    private _gifRecorder: any;
+    private _previousRenderingScale: number;
+    private _crunchingGIF = false;
 
     constructor(props: IPaneComponentProps) {
         super(props);
@@ -63,6 +71,12 @@ export class ToolsTabComponent extends PaneComponent {
             this._videoRecorder.dispose();
             this._videoRecorder = null;
         }
+
+        if (this._gifRecorder) {
+            this._gifRecorder.render();     
+            this._gifRecorder = null; 
+            return;            
+        }
     }
 
     captureScreenshot() {
@@ -106,6 +120,62 @@ export class ToolsTabComponent extends PaneComponent {
         this.setState({ tag: "Stop recording" });
     }
 
+    recordGIFInternal() {
+        const workerUrl = URL.createObjectURL(this._gifWorkerBlob);
+        this._gifRecorder = new GIF({
+            workers: 2,
+            quality: 10,
+            workerScript: workerUrl
+        });
+        const scene = this.props.scene;
+        const engine = scene.getEngine();
+
+        this._previousRenderingScale = engine.getHardwareScalingLevel();
+        engine.setHardwareScalingLevel(engine.getRenderWidth() / this._gifOptions.width | 0);
+
+        let intervalId = setInterval(() => {
+            if (!this._gifRecorder) {
+                clearInterval(intervalId);
+                return;
+            }
+            this._gifRecorder.addFrame(engine.getRenderingCanvas(), {delay: this._gifOptions.frequency});
+        }, this._gifOptions.frequency);
+                        
+        this._gifRecorder.on('finished', (blob: Blob) =>{
+            this._crunchingGIF = false;
+            Tools.Download(blob, "record.gif");
+            
+            this.forceUpdate();
+
+            URL.revokeObjectURL(workerUrl);
+            engine.setHardwareScalingLevel(this._previousRenderingScale);
+        });
+                        
+        this.forceUpdate();
+    }
+
+    recordGIF() {
+        if (this._gifRecorder) {            
+            this._crunchingGIF = true;
+            this.forceUpdate();
+            this._gifRecorder.render();     
+            this._gifRecorder = null; 
+            return;            
+        }
+
+        if (this._gifWorkerBlob) {
+            this.recordGIFInternal();
+            return;
+        }
+
+        Tools.LoadFileAsync("https://cdn.jsdelivr.net/gh//terikon/gif.js.optimized@0.1.6/dist/gif.worker.js").then(value => {
+            this._gifWorkerBlob = new Blob([value], {
+                type: 'application/javascript'
+            });
+            this.recordGIFInternal();
+        });
+    }
+
     importAnimations(event: any) {
 
         const scene = this.props.scene;
@@ -222,18 +292,33 @@ export class ToolsTabComponent extends PaneComponent {
                         <CheckBoxLineComponent label="Use Width/Height" onSelect={ value => {
                             this._useWidthHeight = value;
                             this.forceUpdate();
-                        }
-                        } isSelected={() => this._useWidthHeight} />
+                        }} isSelected={() => this._useWidthHeight} />
                         {
                         this._useWidthHeight &&
                         <div className="secondLine">
                             <NumericInputComponent label="Width" precision={0} step={1} value={this._screenShotSize.width ? this._screenShotSize.width : 512} onChange={value => this._screenShotSize.width = value} />
                             <NumericInputComponent label="Height" precision={0} step={1} value={this._screenShotSize.height ? this._screenShotSize.height : 512} onChange={value => this._screenShotSize.height = value} />
                         </div>
-                        }
-                        
-                    </div>
+                        }      
+                    </div>              
                 </LineContainerComponent>
+                <LineContainerComponent globalState={this.props.globalState} title="GIF">
+                    {
+                        this._crunchingGIF &&
+                        <MessageLineComponent text="Creating the GIF file..." />
+                    }
+                    {
+                        !this._crunchingGIF &&
+                        <ButtonLineComponent label={this._gifRecorder ? "Stop" : "Record"} onClick={() => this.recordGIF()} />
+                    }
+                    {
+                        !this._crunchingGIF && !this._gifRecorder &&
+                        <>
+                            <FloatLineComponent label="Resolution" isInteger={true} target={this._gifOptions} propertyName="width" />
+                            <FloatLineComponent label="Frequency (ms)" isInteger={true} target={this._gifOptions} propertyName="frequency" />
+                        </>
+                    }
+                </LineContainerComponent>                
                 <LineContainerComponent globalState={this.props.globalState} title="REPLAY">
                     <ButtonLineComponent label="Generate replay code" onClick={() => this.exportReplay()} />
                     <ButtonLineComponent label="Reset" onClick={() => this.resetReplay()} />

+ 16 - 0
inspector/src/components/sceneExplorer/sceneExplorerComponent.tsx

@@ -22,6 +22,8 @@ import { NodeMaterial } from 'babylonjs/Materials/Node/nodeMaterial';
 import { ParticleHelper } from 'babylonjs/Particles/particleHelper';
 import { GPUParticleSystem } from 'babylonjs/Particles/gpuParticleSystem';
 import { SSAO2RenderingPipeline } from 'babylonjs/PostProcesses/RenderPipeline/Pipelines/ssao2RenderingPipeline';
+import { StandardMaterial } from 'babylonjs/Materials/standardMaterial';
+import { PBRMaterial } from 'babylonjs/Materials/PBR/pbrMaterial';
 
 require("./sceneExplorer.scss");
 
@@ -312,6 +314,20 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
         // Materials
         let materialsContextMenus: { label: string, action: () => void }[] = [];
         materialsContextMenus.push({
+            label: "Add new standard material",
+            action: () => {
+                let newStdMaterial = new StandardMaterial("Standard material", scene);
+                this.props.globalState.onSelectionChangedObservable.notifyObservers(newStdMaterial);
+            }
+        });        
+        materialsContextMenus.push({
+            label: "Add new PBR material",
+            action: () => {
+                let newPBRMaterial = new PBRMaterial("PBR material", scene);
+                this.props.globalState.onSelectionChangedObservable.notifyObservers(newPBRMaterial);
+            }
+        });        
+        materialsContextMenus.push({
             label: "Add new node material",
             action: () => {
                 let newNodeMaterial = new NodeMaterial("node material", scene);

+ 5 - 4
package.json

@@ -45,6 +45,7 @@
         "@types/mocha": "2.2.46",
         "@types/node": "^10.5.3",
         "@types/react": "~16.7.3",
+        "@types/react-color": "^3.0.1",
         "@types/react-dom": "~16.0.9",
         "@types/sinon": "^4.1.3",
         "ajv": "^6.9.1",
@@ -60,6 +61,7 @@
         "dts-bundle": "^0.7.3",
         "file-loader": "~2.0.0",
         "fs-extra": "^5.0.0",
+        "gif.js.optimized": "^1.0.1",
         "gulp": "^4.0.0",
         "gulp-concat": "~2.6.1",
         "gulp-connect": "^5.6.1",
@@ -85,6 +87,7 @@
         "prompt": "^1.0.0",
         "re-resizable": "~4.9.1",
         "react": "~16.9.0",
+        "react-color": "^2.18.0",
         "react-contextmenu": "~2.10.0",
         "react-dom": "~16.9.0",
         "sass-loader": "^7.1.0",
@@ -105,8 +108,6 @@
         "webpack-dev-server": "^3.1.14",
         "webpack-stream": "~5.2.0",
         "xhr2": "^0.1.4",
-        "xmlbuilder": "8.2.2",
-        "react-color": "^2.18.0",
-        "@types/react-color": "^3.0.1"
+        "xmlbuilder": "8.2.2"
     }
-}
+}