فهرست منبع

Merge pull request #9451 from bghgary/sandbox-texture-viewer

Add ability to view images (ktx2, png, jpg) to the sandbox
Raanan Weber 4 سال پیش
والد
کامیت
c6516e3cf4

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

@@ -7,6 +7,7 @@
 ### General
 
 - Added static CenterToRef for vectors 2/3/4  ([aWeirdo](https://github.com/aWeirdo))
+- Added ability to view images (ktx2, png, jpg) to the sandbox. ([bghgary](https://github.com/bghgary))
 
 ### Loaders
 

+ 7 - 5
inspector/src/components/globalState.ts

@@ -132,11 +132,6 @@ export class GlobalState {
             this.glTFLoaderExtensions[extension.name] = extension;
         });
 
-        if (this.validationResults) {
-            this.validationResults = null;
-            this.onValidationResultsUpdatedObservable.notifyObservers(null);
-        }
-
         loader.onValidatedObservable.add((results: IGLTFValidationResults) => {
             this.validationResults = results;
             this.onValidationResultsUpdatedObservable.notifyObservers(results);
@@ -148,6 +143,13 @@ export class GlobalState {
         });
     }
 
+    public resetGLTFValidationResults() {
+        if (this.validationResults) {
+            this.validationResults = null;
+            this.onValidationResultsUpdatedObservable.notifyObservers(null);
+        }
+    }
+
     // Light gizmos
     public lightGizmos: Array<LightGizmo> = [];
     public enableLightGizmo(light: Light, enable = true) {

+ 2 - 0
inspector/src/inspector.ts

@@ -335,6 +335,8 @@ export class Inspector {
     public static EarlyAttachToLoader() {
         if (!this._GlobalState.onPluginActivatedObserver) {
             this._GlobalState.onPluginActivatedObserver = SceneLoader.OnPluginActivatedObservable.add((rawLoader) => {
+                this._GlobalState.resetGLTFValidationResults();
+
                 const loader = rawLoader as import("babylonjs-loaders/glTF/index").GLTFFileLoader;
                 if (loader.name === "gltf") {
                     this._GlobalState.prepareGLTFPlugin(loader);

+ 93 - 13
sandbox/src/components/renderingZone.tsx

@@ -12,9 +12,26 @@ import { EnvironmentTools } from "../tools/environmentTools";
 import { Tools } from "babylonjs/Misc/tools";
 import { FilesInput } from "babylonjs/Misc/filesInput";
 import { Animation } from "babylonjs/Animations/animation";
+import { PBRBaseMaterial, PBRMaterial, StringTools, Texture } from "babylonjs";
+import { Mesh } from "babylonjs/Meshes/mesh";
 
 require("../scss/renderingZone.scss");
 
+function isTextureAsset(name: string): boolean {
+    var queryStringIndex = name.indexOf("?");
+    if (queryStringIndex !== -1) {
+        name = name.substring(0, queryStringIndex);
+    }
+
+    return (
+        StringTools.EndsWith(name, ".ktx") ||
+        StringTools.EndsWith(name, ".ktx2") ||
+        StringTools.EndsWith(name, ".png") ||
+        StringTools.EndsWith(name, ".jpg") ||
+        StringTools.EndsWith(name, ".jpeg")
+    );
+}
+
 interface IRenderingZoneProps {
     globalState: GlobalState;
     assetUrl?: string;
@@ -23,7 +40,7 @@ interface IRenderingZoneProps {
 }
 
 export class RenderingZone extends React.Component<IRenderingZoneProps> {
-    private _currentPluginName: string;
+    private _currentPluginName?: string;
     private _engine: Engine;
     private _scene: Scene;
     private _canvas: HTMLCanvasElement;
@@ -72,16 +89,41 @@ export class RenderingZone extends React.Component<IRenderingZoneProps> {
             }
         );
 
-        filesInput.onProcessFileCallback = (file, name, extension) => {
+        filesInput.onProcessFileCallback = (file, name, extension, setSceneFileToLoad) => {
             if (filesInput.filesToLoad && filesInput.filesToLoad.length === 1 && extension) {
-                if (extension.toLowerCase() === "dds" || extension.toLowerCase() === "env" || extension.toLowerCase() === "hdr") {
-                    FilesInput.FilesToLoad[name] = file;
-                    EnvironmentTools.SkyboxPath = "file:" + (file as any).correctName;
-                    return false;
+                switch (extension.toLowerCase()) {
+                    case "dds":
+                    case "env":
+                    case "hdr": {
+                        FilesInput.FilesToLoad[name] = file;
+                        EnvironmentTools.SkyboxPath = "file:" + (file as any).correctName;
+                        return false;
+                    }
+                    default: {
+                        if (isTextureAsset(name)) {
+                            setSceneFileToLoad(file);
+                        }
+
+                        break;
+                    }
                 }
             }
+
             return true;
         };
+
+        filesInput.loadAsync = (sceneFile, onProgress) => {
+            const filesToLoad = filesInput.filesToLoad;
+            if (filesToLoad.length === 1) {
+                const fileName = (filesToLoad[0] as any).correctName;
+                if (isTextureAsset(fileName)) {
+                    return Promise.resolve(this.loadTextureAsset(`file:${fileName}`));
+                }
+            }
+
+            return SceneLoader.LoadAsync("file:", sceneFile, this._engine, onProgress);
+        };
+
         filesInput.monitorElementForDragNDrop(this._canvas);
 
         this.props.globalState.filesInput = filesInput;
@@ -176,8 +218,8 @@ export class RenderingZone extends React.Component<IRenderingZoneProps> {
             }
         } else {
             var pbrPresent = false;
-            for (var i = 0; i < this._scene.materials.length; i++) {
-                if (this._scene.materials[i].transparencyMode !== undefined) {
+            for (const material of this._scene.materials) {
+                if (material instanceof PBRBaseMaterial) {
                     pbrPresent = true;
                     break;
                 }
@@ -207,13 +249,52 @@ export class RenderingZone extends React.Component<IRenderingZoneProps> {
         if (this.props.globalState.isDebugLayerEnabled) {
             this.props.globalState.showDebugLayer();
         }
+
+        delete this._currentPluginName;
+    }
+
+    loadTextureAsset(url: string): Scene {
+        const scene = new Scene(this._engine);
+        const plane = Mesh.CreatePlane("plane", 1, scene);
+
+        const texture = new Texture(url, scene, undefined, undefined, Texture.NEAREST_LINEAR, () => {
+            const size = texture.getBaseSize();
+            if (size.width > size.height) {
+                plane.scaling.y = size.height / size.width;
+            } else {
+                plane.scaling.x = size.width / size.height;
+            }
+
+            texture.gammaSpace = true;
+            texture.hasAlpha = true;
+            texture.wrapU = Texture.CLAMP_ADDRESSMODE;
+            texture.wrapV = Texture.CLAMP_ADDRESSMODE;
+
+            scene.debugLayer.show();
+            scene.debugLayer.select(texture, "PREVIEW");
+        }, (message, exception) => {
+            this.props.globalState.onError.notifyObservers({ scene: scene, message: message || exception.message || "Failed to load texture" });
+        });
+
+        const material = new PBRMaterial("unlit", scene);
+        material.unlit = true;
+        material.albedoTexture = texture;
+        material.alphaMode = PBRMaterial.PBRMATERIAL_ALPHABLEND;
+        plane.material = material;
+
+        return scene;
     }
 
     loadAssetFromUrl() {
-        let assetUrl = this.props.assetUrl!;
-        let rootUrl = Tools.GetFolderPath(assetUrl);
-        let fileName = Tools.GetFilename(assetUrl);
-        SceneLoader.LoadAsync(rootUrl, fileName, this._engine)
+        const assetUrl = this.props.assetUrl!;
+        const rootUrl = Tools.GetFolderPath(assetUrl);
+        const fileName = Tools.GetFilename(assetUrl);
+
+        const promise = isTextureAsset(assetUrl)
+            ? Promise.resolve(this.loadTextureAsset(assetUrl))
+            : SceneLoader.LoadAsync(rootUrl, fileName, this._engine);
+
+        promise
             .then((scene) => {
                 if (this._scene) {
                     this._scene.dispose();
@@ -231,7 +312,6 @@ export class RenderingZone extends React.Component<IRenderingZoneProps> {
             })
             .catch((reason) => {
                 this.props.globalState.onError.notifyObservers({ message: reason.message });
-                //TODO sceneError({ name: fileName }, null, reason.message || reason);
             });
     }
 

+ 27 - 27
sandbox/src/sandbox.tsx

@@ -12,14 +12,14 @@ var fullScreenLogo = require("./img/logo-fullscreen.svg");
 interface ISandboxProps {
 }
 
-export class Sandbox extends React.Component<ISandboxProps, {isFooterVisible: boolean, errorMessage: string}> {
+export class Sandbox extends React.Component<ISandboxProps, { isFooterVisible: boolean, errorMessage: string }> {
     private _globalState: GlobalState;
-    private _assetUrl?: string;    
+    private _assetUrl?: string;
     private _cameraPosition?: Vector3;
-    private _logoRef: React.RefObject<HTMLImageElement>;    
+    private _logoRef: React.RefObject<HTMLImageElement>;
     private _dropTextRef: React.RefObject<HTMLDivElement>;
     private _clickInterceptorRef: React.RefObject<HTMLDivElement>;
-    
+
     public constructor(props: ISandboxProps) {
         super(props);
         this._globalState = new GlobalState();
@@ -27,14 +27,14 @@ export class Sandbox extends React.Component<ISandboxProps, {isFooterVisible: bo
         this._dropTextRef = React.createRef();
         this._clickInterceptorRef = React.createRef();
 
-        this.state = {isFooterVisible: true, errorMessage: ""};
-        
+        this.state = { isFooterVisible: true, errorMessage: "" };
+
         this.checkUrl();
 
         EnvironmentTools.HookWithEnvironmentChange(this._globalState);
 
         // Events
-        this._globalState.onSceneLoaded.add(info => {
+        this._globalState.onSceneLoaded.add((info) => {
             document.title = "Babylon.js - " + info.filename;
 
             this._globalState.currentScene = info.scene;
@@ -47,13 +47,13 @@ export class Sandbox extends React.Component<ISandboxProps, {isFooterVisible: bo
             }
         });
 
-        this._globalState.onError.add(error => {
+        this._globalState.onError.add((error) => {
             if (error.scene) {
                 this._globalState.showDebugLayer();
             }
 
             if (error.message) {
-                this.setState({errorMessage: error.message});
+                this.setState({ errorMessage: error.message });
             }
         });
 
@@ -68,10 +68,10 @@ export class Sandbox extends React.Component<ISandboxProps, {isFooterVisible: bo
         });
 
         // Keyboard
-        window.addEventListener("keydown", (event: KeyboardEvent) =>{
+        window.addEventListener("keydown", (event: KeyboardEvent) => {
             // Press space to toggle footer
             if (event.keyCode === 32 && event.target && (event.target as HTMLElement).nodeName !== "INPUT") {
-                this.setState({isFooterVisible: !this.state.isFooterVisible});
+                this.setState({ isFooterVisible: !this.state.isFooterVisible });
             }
         });
     }
@@ -91,11 +91,11 @@ export class Sandbox extends React.Component<ISandboxProps, {isFooterVisible: bo
                         break;
                     }
                     case "cameraPosition": {
-                        this._cameraPosition = Vector3.FromArray(value.split(",").map(function(component) { return +component; }));
+                        this._cameraPosition = Vector3.FromArray(value.split(",").map(function (component) { return +component; }));
                         break;
                     }
                     case "kiosk": {
-                        this.state = {isFooterVisible: value === "true" ? false : true, errorMessage: ""};
+                        this.state = { isFooterVisible: value === "true" ? false : true, errorMessage: "" };
                         break;
                     }
                 }
@@ -113,11 +113,11 @@ export class Sandbox extends React.Component<ISandboxProps, {isFooterVisible: bo
         return (
             <div id="root">
                 <p id="droptext" ref={this._dropTextRef}>Drag and drop gltf, glb, obj or babylon files to view them</p>
-                <RenderingZone globalState={this._globalState} 
-                    assetUrl={this._assetUrl} 
-                    cameraPosition={this._cameraPosition} 
-                    expanded={!this.state.isFooterVisible}/>                
-                <div ref={this._clickInterceptorRef} 
+                <RenderingZone globalState={this._globalState}
+                    assetUrl={this._assetUrl}
+                    cameraPosition={this._cameraPosition}
+                    expanded={!this.state.isFooterVisible} />
+                <div ref={this._clickInterceptorRef}
                     onClick={() => {
                         this._globalState.onClickInterceptorClicked.notifyObservers();
                         this._clickInterceptorRef.current!.classList.add("hidden");
@@ -128,26 +128,26 @@ export class Sandbox extends React.Component<ISandboxProps, {isFooterVisible: bo
                     <Footer globalState={this._globalState} />
                 }
                 <div id="logoContainer">
-                    <img id="logo" src={fullScreenLogo} ref={this._logoRef}/>
-                </div>                      
+                    <img id="logo" src={fullScreenLogo} ref={this._logoRef} />
+                </div>
                 {
                     this.state.errorMessage &&
                     <div id="errorZone">
                         <div className="message">
                             {this.state.errorMessage}
                         </div>
-                        <button type="button" className="close" 
-                            onClick={() => this.setState({errorMessage: ""})}
+                        <button type="button" className="close"
+                            onClick={() => this.setState({ errorMessage: "" })}
                             data-dismiss="alert">&times;</button>
-                    </div>                           
-                } 
-            </div>   
-        )
+                    </div>
+                }
+            </div>
+        );
     }
 
     public static Show(hostElement: HTMLElement) {
         const sandBox = React.createElement(Sandbox, {});
-        
+
         ReactDOM.render(sandBox, hostElement);
     }
 }

+ 9 - 7
src/Misc/filesInput.ts

@@ -19,7 +19,12 @@ export class FilesInput {
     /**
      * Callback called when a file is processed
      */
-    public onProcessFileCallback: (file: File, name: string, extension: string) => boolean = () => { return true; };
+    public onProcessFileCallback: (file: File, name: string, extension: string, setSceneFileToLoad: (sceneFile: File) => void) => boolean = () => { return true; };
+
+    /**
+     * Function used when loading the scene file
+     */
+    public loadAsync: (sceneFile: File, onProgress: Nullable<(event: ISceneLoaderProgressEvent) => void>) => Promise<Scene> = (sceneFile, onProgress) => SceneLoader.LoadAsync("file", sceneFile, this._engine, onProgress);
 
     private _engine: Engine;
     private _currentScene: Nullable<Scene>;
@@ -168,7 +173,7 @@ export class FilesInput {
             var name = files[i].correctName.toLowerCase();
             var extension = name.split('.').pop();
 
-            if (!this.onProcessFileCallback(files[i], name, extension)) {
+            if (!this.onProcessFileCallback(files[i], name, extension, (sceneFile) => this._sceneFileToLoad = sceneFile)) {
                 continue;
             }
 
@@ -278,11 +283,8 @@ export class FilesInput {
 
             SceneLoader.ShowLoadingScreen = false;
             this._engine.displayLoadingUI();
-            SceneLoader.LoadAsync("file:", this._sceneFileToLoad, this._engine, (progress) => {
-                if (this._progressCallback) {
-                    this._progressCallback(progress);
-                }
-            }).then((scene) => {
+
+            this.loadAsync(this._sceneFileToLoad, this._progressCallback).then((scene) => {
                 if (this._currentScene) {
                     this._currentScene.dispose();
                 }