Bläddra i källkod

Texture Viewer (#8495)

* texture inspector functionality

* rename edit to view

* clean up unused code

* move up popup component & update whatsnew

* use snippet server for material

* fix keyboard events

* focus canvas on load

* focus canvas on load

* remove floodfill
DarraghBurkeMS 5 år sedan
förälder
incheckning
6afbd7c39a

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

@@ -67,6 +67,7 @@
 - Added support for recording GIF ([Deltakosh](https://github.com/deltakosh))
 - Popup Window available (To be used in Curve Editor) ([pixelspace](https://github.com/devpixelspace))
 - Add support to update inspector when switching to a new scene ([belfortk](https://github.com/belfortk))
+- View textures in pop out window with zoom & pan ([DarraghBurkeMS](https://github.com/DarraghBurkeMS))
 
 ### Cameras
 

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

@@ -19,7 +19,7 @@ import { FloatLineComponent } from '../../../lines/floatLineComponent';
 import { TextLineComponent } from '../../../lines/textLineComponent';
 import { IAnimatable } from 'babylonjs/Animations/animatable.interface';
 import { AnimationCurveEditorComponent } from '../animations/animationCurveEditorComponent';
-import { PopupComponent } from '../animations/popupComponent';
+import { PopupComponent } from '../../../../popupComponent';
 
 interface IAnimationGridComponentProps {
   globalState: GlobalState;

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

@@ -11,7 +11,7 @@ import { TextLineComponent } from '../../../lines/textLineComponent';
 import { LockObject } from '../lockObject';
 import { GlobalState } from '../../../../globalState';
 import { TextInputLineComponent } from '../../../lines/textInputLineComponent';
-import { PopupComponent } from '../animations/popupComponent';
+import { PopupComponent } from '../../../../popupComponent';
 import { AnimationCurveEditorComponent } from '../animations/animationCurveEditorComponent';
 import { AnimationGroup } from 'babylonjs/Animations/animationGroup';
 

+ 42 - 5
inspector/src/components/actionTabs/tabs/propertyGrids/materials/texturePropertyGridComponent.tsx

@@ -27,6 +27,10 @@ import { ButtonLineComponent } from '../../../lines/buttonLineComponent';
 import { TextInputLineComponent } from '../../../lines/textInputLineComponent';
 import { AnimationGridComponent } from '../animations/animationPropertyGridComponent';
 
+import { Engine } from 'babylonjs/Engines/engine';
+import { PopupComponent } from '../../../../popupComponent';
+import { TextureEditorComponent } from './textures/textureEditorComponent';
+
 interface ITexturePropertyGridComponentProps {
     texture: BaseTexture,
     lockObject: LockObject,
@@ -38,6 +42,8 @@ export class TexturePropertyGridComponent extends React.Component<ITextureProper
 
     private _adtInstrumentation: Nullable<AdvancedDynamicTextureInstrumentation>;
     private textureLineRef: React.RefObject<TextureLineComponent>;
+
+    private _isTextureEditorOpen = false;
     
 
     constructor(props: ITexturePropertyGridComponentProps) {
@@ -83,16 +89,27 @@ export class TexturePropertyGridComponent extends React.Component<ITextureProper
                         extension = ".env";
                     }
 
-                    (texture as CubeTexture).updateURL(base64data, extension, () => this.foreceRefresh());
+                    (texture as CubeTexture).updateURL(base64data, extension, () => this.forceRefresh());
                 } else {
-                    (texture as Texture).updateURL(base64data, null, () => this.foreceRefresh());
+                    (texture as Texture).updateURL(base64data, null, () => this.forceRefresh());
                 }
             };
 
         }, undefined, true);
-    }    
+    }
+
+    onOpenTextureEditor() {
+        this._isTextureEditorOpen = true;
+    }
+    
+    onCloseTextureEditor(window: Window | null) {
+        this._isTextureEditorOpen = false;
+        if (window !== null) {
+            window.close();
+        }
+    }
 
-    foreceRefresh() {
+    forceRefresh() {
         this.forceUpdate();
         (this.textureLineRef.current as TextureLineComponent).updatePreview();
     }
@@ -132,16 +149,36 @@ export class TexturePropertyGridComponent extends React.Component<ITextureProper
             }
         }
 
+        const editable = texture.textureType != Engine.TEXTURETYPE_FLOAT && texture.textureType != Engine.TEXTURETYPE_FLOAT_32_UNSIGNED_INT_24_8_REV && texture.textureType !== Engine.TEXTURETYPE_HALF_FLOAT;
+
         return (
             <div className="pane">
                 <LineContainerComponent globalState={this.props.globalState} title="PREVIEW">
                     <TextureLineComponent ref={this.textureLineRef} texture={texture} width={256} height={256} globalState={this.props.globalState} />
                     <FileButtonLineComponent label="Load texture from file" onClick={(file) => this.updateTexture(file)} accept=".jpg, .png, .tga, .dds, .env" />
+                    {editable &&
+                        <ButtonLineComponent label="View" onClick={() => this.onOpenTextureEditor()} />
+                    }
                     <TextInputLineComponent label="URL" value={textureUrl} lockObject={this.props.lockObject} onChange={url => {
                         (texture as Texture).updateURL(url);
-                        this.foreceRefresh();
+                        this.forceRefresh();
                     }} />
                 </LineContainerComponent>
+                {this._isTextureEditorOpen && (
+                <PopupComponent
+                  id='texture-editor'
+                  title='Texture Editor'
+                  size={{ width: 1024, height: 490 }}
+                  onOpen={(window: Window) => {}}
+                  onClose={(window: Window) =>
+                    this.onCloseTextureEditor(window)
+                  }
+                >
+                    <TextureEditorComponent
+                        globalState={this.props.globalState}
+                        texture={this.props.texture}
+                    />
+                </PopupComponent>)}
                 <CustomPropertyGridComponent globalState={this.props.globalState} target={texture}
                     lockObject={this.props.lockObject}
                     onPropertyChangedObservable={this.props.onPropertyChangedObservable} />

+ 156 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/textureCanvasManager.ts

@@ -0,0 +1,156 @@
+import { Engine } from 'babylonjs/Engines/engine';
+import { Scene } from 'babylonjs/scene';
+import { Vector3 } from 'babylonjs/Maths/math.vector';
+import { Color4 } from 'babylonjs/Maths/math.color';
+import { FreeCamera } from 'babylonjs/Cameras/freeCamera';
+
+import { PlaneBuilder } from 'babylonjs/Meshes/Builders/planeBuilder';
+import { Mesh } from 'babylonjs/Meshes/mesh';
+import { Camera } from 'babylonjs/Cameras/camera';
+import { DynamicTexture } from 'babylonjs/Materials/Textures/dynamicTexture';
+import { BaseTexture } from 'babylonjs/Materials/Textures/baseTexture';
+import { NodeMaterial } from 'babylonjs/Materials/Node/nodeMaterial';
+
+import { PointerEventTypes } from 'babylonjs/Events/pointerEvents';
+import { KeyboardEventTypes } from 'babylonjs/Events/keyboardEvents';
+
+export class TextureCanvasManager {
+    private _engine: Engine;
+    private _scene: Scene;
+    private _texture: DynamicTexture;
+    private _camera: FreeCamera;
+    private _canvas : HTMLCanvasElement;
+
+    private _scale : number;
+    private _isPanning : boolean;
+    private _mouseX : number;
+    private _mouseY : number;
+
+    private _plane : Mesh;
+    private _planeMaterial : NodeMaterial;
+
+    private static ZOOM_MOUSE_SPEED : number = 0.0005;
+    private static ZOOM_KEYBOARD_SPEED : number = 0.2;
+    private static PAN_SPEED : number = 0.002;
+    private static PAN_BUTTON : number = 0; // left mouse button
+    private static MIN_SCALE : number = 0.01;
+    private static MAX_SCALE : number = 10;
+
+    public constructor(targetCanvas: HTMLCanvasElement, texture: BaseTexture) {
+        this._canvas = targetCanvas;
+
+        this._engine = new Engine(targetCanvas, true);
+        this._scene = new Scene(this._engine);
+        this._scene.clearColor = new Color4(0.2, 0.2, 0.2, 1.0);
+
+        this._camera = new FreeCamera("Camera", new Vector3(0, 0, -1), this._scene);
+        this._camera.mode = Camera.ORTHOGRAPHIC_CAMERA;
+
+        if (texture) {
+            /* Grab image data from original texture and paint it onto the context of a DynamicTexture */
+            const pixelData = texture.readPixels()!;
+            const arr = new Uint8ClampedArray(pixelData.buffer);
+            let imgData = new ImageData(arr, texture.getSize().width, texture.getSize().height);
+            this._texture = new DynamicTexture("texture", texture.getSize(), this._scene, false);
+            const ctx = this._texture.getContext();
+            ctx.putImageData(imgData, 0, 0);
+            this._texture.update();
+            this._texture.hasAlpha = texture.hasAlpha;
+        } else {
+            /* If we don't have a texture to start with, just generate a white rectangle */
+            this._texture = new DynamicTexture("texture",  256, this._scene, false);
+            const ctx = this._texture.getContext();
+            ctx.fillStyle = 'white';
+            ctx.fillRect(0, 0, 256, 256);
+            this._texture.update();
+        }
+
+        this._texture.updateSamplingMode(Engine.TEXTURE_NEAREST_LINEAR);
+        const textureRatio = this._texture.getSize().width / this._texture.getSize().height;
+
+        this._plane = PlaneBuilder.CreatePlane("plane", {width: textureRatio, height: 1}, this._scene);
+        NodeMaterial.ParseFromSnippetAsync("#TPSEV2#3", this._scene)
+            .then((material) => {
+                this._planeMaterial = material;
+                this._planeMaterial.getTextureBlocks()[0].texture = this._texture;
+                this._plane.material = this._planeMaterial;
+                this._canvas.focus();
+            });
+        this._plane.enableEdgesRendering();
+        this._plane.edgesWidth = 4.0;
+        this._plane.edgesColor = new Color4(1,1,1,1);
+
+        this._engine.runRenderLoop(() => {
+            this._engine.resize();
+            this._scene.render();
+
+        });
+
+        this._scale = 1;
+        this._isPanning = false;
+
+        this._scene.onBeforeRenderObservable.add(() => {
+            let ratio = this._canvas?.width / this._canvas?.height;
+            this._camera.orthoBottom = -this._scale;
+            this._camera.orthoTop = this._scale;
+            this._camera.orthoLeft = -this._scale * ratio;
+            this._camera.orthoRight = this._scale * ratio;
+        })
+
+        this._scene.onPointerObservable.add((pointerInfo) => {
+            switch (pointerInfo.type) {
+                case PointerEventTypes.POINTERWHEEL:
+                    const event = pointerInfo.event as MouseWheelEvent;
+                    this._scale += (event.deltaY * TextureCanvasManager.ZOOM_MOUSE_SPEED * this._scale);
+                    this._scale = Math.min(Math.max(this._scale, TextureCanvasManager.MIN_SCALE), TextureCanvasManager.MAX_SCALE);
+                    break;
+                case PointerEventTypes.POINTERDOWN:
+                    if (pointerInfo.event.button === TextureCanvasManager.PAN_BUTTON) {
+                        this._isPanning = true;
+                        this._mouseX = pointerInfo.event.x;
+                        this._mouseY = pointerInfo.event.y;
+                    }
+                    break;
+                case PointerEventTypes.POINTERUP:
+                    if (pointerInfo.event.button === TextureCanvasManager.PAN_BUTTON) {
+                        this._isPanning = false;
+                    }
+                    break;
+                case PointerEventTypes.POINTERMOVE:
+                    if (this._isPanning) {
+                        this._camera.position.x -= (pointerInfo.event.x - this._mouseX) * this._scale * TextureCanvasManager.PAN_SPEED;
+                        this._camera.position.y += (pointerInfo.event.y - this._mouseY) * this._scale * TextureCanvasManager.PAN_SPEED;
+                        this._mouseX = pointerInfo.event.x;
+                        this._mouseY = pointerInfo.event.y;
+                    }
+                    break;
+            }
+        })
+
+        this._scene.onKeyboardObservable.add((kbInfo) => {
+            switch(kbInfo.type) {
+                case KeyboardEventTypes.KEYDOWN:
+                    if (kbInfo.event.key == "+") {
+                        this._scale -= TextureCanvasManager.ZOOM_KEYBOARD_SPEED * this._scale;
+                    }
+                    if (kbInfo.event.key == "-") {
+                        this._scale += TextureCanvasManager.ZOOM_KEYBOARD_SPEED * this._scale;
+                    }
+                    this._scale = Math.min(Math.max(this._scale, TextureCanvasManager.MIN_SCALE), TextureCanvasManager.MAX_SCALE);
+                    break;
+            }
+        })
+
+    }
+
+    public dispose() {
+        if (this._planeMaterial) {
+            this._planeMaterial.dispose();
+        }
+        this._texture.dispose();
+        this._plane.dispose();
+        this._camera.dispose();
+        this._scene.dispose();
+        this._engine.dispose();
+    }
+} 

+ 4 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/textureEditor.scss

@@ -0,0 +1,4 @@
+#texture-canvas {
+    width: 100%;
+    height: 100%;
+}

+ 30 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/textureEditorComponent.tsx

@@ -0,0 +1,30 @@
+import * as React from 'react';
+import { GlobalState } from '../../../../../globalState';
+import { BaseTexture } from 'babylonjs/Materials/Textures/baseTexture';
+import { TextureCanvasManager } from './textureCanvasManager';
+
+require('./textureEditor.scss');
+
+interface TextureEditorComponentProps {
+    globalState: GlobalState;
+    texture: BaseTexture;
+}
+
+export class TextureEditorComponent extends React.Component<TextureEditorComponentProps> {
+    private _textureCanvasManager: TextureCanvasManager;
+    private reactCanvas = React.createRef<HTMLCanvasElement>();
+
+    componentDidMount() {
+        this._textureCanvasManager = new TextureCanvasManager(this.reactCanvas.current!, this.props.texture);
+    }
+
+    componentWillUnmount() {
+        this._textureCanvasManager.dispose();
+    }
+
+    render() {
+        return <div id='texture-editor'>
+            <canvas id="texture-canvas" ref={this.reactCanvas} tabIndex={1}></canvas>
+        </div>
+    }
+}

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

@@ -1,6 +1,6 @@
 import * as React from "react";
 import * as ReactDOM from 'react-dom';
-import { Inspector } from '../../../../../inspector';
+import { Inspector } from '../inspector';
 
 interface IPopupComponentProps {
     id: string,