Bläddra i källkod

inspector light gizmo

Trevor Baron 6 år sedan
förälder
incheckning
aaa1af5bf7

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

@@ -96,6 +96,7 @@
 - Added per solid particle culling possibility : `solidParticle.isInFrustum()`  ([jerome](https://github.com/jbousquie))
 - Added transparency support to `GlowLayer` ([Sebavan](https://github.com/Sebavan))
 - Added option `forceDisposeChildren` to multiMaterial.dispose ([danjpar](https://github.com/danjpar))
+- Inspector light gizmo ([TrevorDev](https://github.com/TrevorDev))
 
 ### OBJ Loader
 - Add color vertex support (not part of standard) ([brianzinn](https://github.com/brianzinn))

+ 25 - 1
inspector/src/components/globalState.ts

@@ -5,7 +5,9 @@ import { Nullable } from "babylonjs/types";
 import { Observable, Observer } from "babylonjs/Misc/observable";
 import { ISceneLoaderPlugin, ISceneLoaderPluginAsync } from "babylonjs/Loading/sceneLoader";
 import { Scene } from "babylonjs/scene";
-
+import { Light } from "babylonjs/Lights/light";
+import { LightGizmo } from "babylonjs/Gizmos/lightGizmo";
+import { GizmoManager } from 'babylonjs';
 import { PropertyChangedEvent } from "./propertyChangedEvent";
 
 export class GlobalState {
@@ -51,4 +53,26 @@ export class GlobalState {
             }
         });
     }
+
+    // Gizmos
+    public gizmoManager:Nullable<GizmoManager> = null;
+
+    // Light gizmos
+    public lightGizmos:Array<LightGizmo> = [];
+    public enableLightGizmo(light:Light, enable = true){
+        if(enable){
+            if(!light.reservedDataStore){
+                light.reservedDataStore = {}
+            }
+            if(!light.reservedDataStore.lightGizmo){
+                light.reservedDataStore.lightGizmo = new LightGizmo();
+                this.lightGizmos.push(light.reservedDataStore.lightGizmo)
+                light.reservedDataStore.lightGizmo.light = light;
+            }
+        }else if(light.reservedDataStore && light.reservedDataStore.lightGizmo){
+            this.lightGizmos.splice(this.lightGizmos.indexOf(light.reservedDataStore.lightGizmo),1);
+            light.reservedDataStore.lightGizmo.dispose();
+            light.reservedDataStore.lightGizmo=null;
+        }
+    }
 }

+ 19 - 4
inspector/src/components/sceneExplorer/entities/lightTreeItemComponent.tsx

@@ -2,7 +2,7 @@ import { IExplorerExtensibilityGroup } from "babylonjs/Debug/debugLayer";
 import { Light } from "babylonjs/Lights/light";
 
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faLightbulb } from '@fortawesome/free-solid-svg-icons';
+import { faLightbulb, faEye } from '@fortawesome/free-solid-svg-icons';
 import { faLightbulb as faLightbubRegular } from '@fortawesome/free-regular-svg-icons';
 import { TreeItemLabelComponent } from "../treeItemLabelComponent";
 import { ExtensionsComponent } from "../extensionsComponent";
@@ -16,13 +16,13 @@ interface ILightTreeItemComponentProps {
     globalState: GlobalState
 }
 
-export class LightTreeItemComponent extends React.Component<ILightTreeItemComponentProps, { isEnabled: boolean }> {
+export class LightTreeItemComponent extends React.Component<ILightTreeItemComponentProps, { isEnabled: boolean, isGizmoEnabled:boolean }> {
     constructor(props: ILightTreeItemComponentProps) {
         super(props);
 
         const light = this.props.light;
 
-        this.state = { isEnabled: light.isEnabled() };
+        this.state = { isEnabled: light.isEnabled(), isGizmoEnabled: (light.reservedDataStore && light.reservedDataStore.lightGizmo) };
     }
 
     switchIsEnabled(): void {
@@ -33,15 +33,30 @@ export class LightTreeItemComponent extends React.Component<ILightTreeItemCompon
         this.setState({ isEnabled: light.isEnabled() });
     }
 
+    toggleGizmo(): void {
+        const light = this.props.light;
+        if(light.reservedDataStore && light.reservedDataStore.lightGizmo){
+            this.props.globalState.enableLightGizmo(light, false);
+            this.setState({ isGizmoEnabled: false });
+        }else{
+            this.props.globalState.enableLightGizmo(light, true);
+            this.setState({ isGizmoEnabled: true });
+        }
+    }
+
     render() {
         const isEnabledElement = this.state.isEnabled ? <FontAwesomeIcon icon={faLightbubRegular} /> : <FontAwesomeIcon icon={faLightbubRegular} className="isNotActive" />;
+        const isGizmoEnabled = this.state.isGizmoEnabled ? <FontAwesomeIcon icon={faEye} /> : <FontAwesomeIcon icon={faEye} className="isNotActive" />;
 
         return (
             <div className="lightTools">
                 <TreeItemLabelComponent label={this.props.light.name} onClick={() => this.props.onClick()} icon={faLightbulb} color="yellow" />
-                <div className="enableLight icon" onClick={() => this.switchIsEnabled()} title="Turn on/off the light">
+                <div className="visibility icon" onClick={() => this.switchIsEnabled()} title="Turn on/off the light">
                     {isEnabledElement}
                 </div>
+                <div className="enableGizmo icon" onClick={() => this.toggleGizmo()} title="Turn on/off the light's gizmo">
+                    {isGizmoEnabled}
+                </div>
                 {
                     <ExtensionsComponent target={this.props.light} extensibilityGroups={this.props.extensibilityGroups} />
                 }

+ 63 - 12
inspector/src/components/sceneExplorer/entities/sceneTreeItemComponent.tsx

@@ -10,26 +10,29 @@ import { faSyncAlt, faImage, faCrosshairs, faArrowsAlt, faCompress, faRedoAlt }
 import { ExtensionsComponent } from "../extensionsComponent";
 import * as React from "react";
 
+import { GlobalState } from "../../globalState";
+import { UtilityLayerRenderer } from 'babylonjs';
+
 interface ISceneTreeItemComponentProps {
     scene: Scene;
     onRefresh: () => void;
     selectedEntity?: any;
     extensibilityGroups?: IExplorerExtensibilityGroup[];
     onSelectionChangedObservable?: Observable<any>;
+    globalState: GlobalState;
 }
 
 export class SceneTreeItemComponent extends React.Component<ISceneTreeItemComponentProps, { isSelected: boolean, isInPickingMode: boolean, gizmoMode: number }> {
+    private _gizmoLayerOnPointerObserver: Nullable<Observer<PointerInfo>>;
     private _onPointerObserver: Nullable<Observer<PointerInfo>>;
     private _onSelectionChangeObserver: Nullable<Observer<any>>;
     private _selectedEntity: any;
 
     constructor(props: ISceneTreeItemComponentProps) {
         super(props);
-
-        const scene = this.props.scene;
         let gizmoMode = 0;
-        if (scene.reservedDataStore && scene.reservedDataStore.gizmoManager) {
-            const manager: GizmoManager = scene.reservedDataStore.gizmoManager;
+        if (props.globalState.gizmoManager) {
+            const manager: GizmoManager = props.globalState.gizmoManager;
             if (manager.positionGizmoEnabled) {
                 gizmoMode = 1;
             } else if (manager.rotationGizmoEnabled) {
@@ -63,13 +66,15 @@ export class SceneTreeItemComponent extends React.Component<ISceneTreeItemCompon
         const scene = this.props.scene;
         this._onSelectionChangeObserver = this.props.onSelectionChangedObservable.add((entity) => {
             this._selectedEntity = entity;
-            if (scene.reservedDataStore && scene.reservedDataStore.gizmoManager) {
-                const manager: GizmoManager = scene.reservedDataStore.gizmoManager;
+            if (scene.reservedDataStore && this.props.globalState.gizmoManager) {
+                const manager: GizmoManager = this.props.globalState.gizmoManager;
 
                 const className = entity.getClassName();
-
+                
                 if (className === "TransformNode" || className.indexOf("Mesh") !== -1) {
                     manager.attachToMesh(entity);
+                }else if(className.indexOf("Light") !== -1 && this._selectedEntity.reservedDataStore && this._selectedEntity.reservedDataStore.lightGizmo){
+                    manager.attachToMesh(this._selectedEntity.reservedDataStore.lightGizmo.attachedMesh);
                 }
             }
         });
@@ -83,6 +88,11 @@ export class SceneTreeItemComponent extends React.Component<ISceneTreeItemCompon
             this._onPointerObserver = null;
         }
 
+        if (this._gizmoLayerOnPointerObserver) {
+            scene.onPointerObservable.remove(this._gizmoLayerOnPointerObserver);
+            this._gizmoLayerOnPointerObserver = null;
+        }
+
         if (this._onSelectionChangeObserver && this.props.onSelectionChangedObservable) {
             this.props.onSelectionChangedObservable.remove(this._onSelectionChangeObserver);
         }
@@ -108,10 +118,27 @@ export class SceneTreeItemComponent extends React.Component<ISceneTreeItemCompon
             this._onPointerObserver = scene.onPointerObservable.add(() => {
                 const pickPosition = scene.unTranslatedPointer;
                 const pickInfo = scene.pick(pickPosition.x, pickPosition.y, (mesh) => mesh.isEnabled() && mesh.isVisible && mesh.getTotalVertices() > 0);
-
+                
+                // Pick light gizmos first
+                if(this.props.globalState.lightGizmos.length > 0){
+                    var gizmoScene = this.props.globalState.lightGizmos[0].gizmoLayer.utilityLayerScene;
+                    let pickInfo = gizmoScene.pick(pickPosition.x, pickPosition.y, (m:any)=>{
+                        for(var g of (this.props.globalState.lightGizmos as any)){
+                            if(g.attachedMesh == m){
+                                return true;
+                            }
+                        }
+                        return false;
+                    });
+                    if (pickInfo && pickInfo.hit && this.props.onSelectionChangedObservable) {
+                        this.props.onSelectionChangedObservable.notifyObservers(pickInfo.pickedMesh);
+                        return;
+                    }
+                }
                 if (pickInfo && pickInfo.hit && this.props.onSelectionChangedObservable) {
                     this.props.onSelectionChangedObservable.notifyObservers(pickInfo.pickedMesh);
                 }
+
             }, PointerEventTypes.POINTERTAP);
         }
 
@@ -125,11 +152,33 @@ export class SceneTreeItemComponent extends React.Component<ISceneTreeItemCompon
             scene.reservedDataStore = {};
         }
 
-        if (!scene.reservedDataStore.gizmoManager) {
-            scene.reservedDataStore.gizmoManager = new GizmoManager(scene);
+        if (this._gizmoLayerOnPointerObserver) {
+            scene.onPointerObservable.remove(this._gizmoLayerOnPointerObserver);
+            this._gizmoLayerOnPointerObserver = null;
+        }
+
+        if (!this.props.globalState.gizmoManager) {
+            this.props.globalState.gizmoManager = new GizmoManager(scene);
         }
 
-        const manager: GizmoManager = scene.reservedDataStore.gizmoManager;
+        const manager: GizmoManager = this.props.globalState.gizmoManager;
+        // Allow picking of light gizmo when a gizmo mode is selected
+        this._gizmoLayerOnPointerObserver = UtilityLayerRenderer.DefaultUtilityLayer.utilityLayerScene.onPointerObservable.add((pointerInfo)=>{
+            if (pointerInfo.type == PointerEventTypes.POINTERDOWN) {
+                if (pointerInfo.pickInfo && pointerInfo.pickInfo.pickedMesh) {
+                    var node: Nullable<any> = pointerInfo.pickInfo.pickedMesh;
+                    // Attach to the most parent node
+                    while (node && node.parent != null) {
+                        node = node.parent;
+                    }
+                    for(var gizmo of this.props.globalState.lightGizmos){
+                        if(gizmo._rootMesh == node){
+                            manager.attachToMesh(gizmo.attachedMesh);
+                        }
+                    }
+                }
+            }
+        })
 
         manager.positionGizmoEnabled = false;
         manager.rotationGizmoEnabled = false;
@@ -138,7 +187,7 @@ export class SceneTreeItemComponent extends React.Component<ISceneTreeItemCompon
         if (this.state.gizmoMode === mode) {
             mode = 0;
             manager.dispose();
-            scene.reservedDataStore.gizmoManager = null;
+            this.props.globalState.gizmoManager = null;
         } else {
             switch (mode) {
                 case 1:
@@ -157,6 +206,8 @@ export class SceneTreeItemComponent extends React.Component<ISceneTreeItemCompon
 
                 if (className === "TransformNode" || className.indexOf("Mesh") !== -1) {
                     manager.attachToMesh(this._selectedEntity);
+                } else if(className.indexOf("Light") !== -1 && this._selectedEntity.reservedDataStore && this._selectedEntity.reservedDataStore.lightGizmo){
+                    manager.attachToMesh(this._selectedEntity.reservedDataStore.lightGizmo.attachedMesh);
                 }
             }
         }

+ 1 - 1
inspector/src/components/sceneExplorer/sceneExplorerComponent.tsx

@@ -207,7 +207,7 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
         return (
             <div id="tree">
                 <SceneExplorerFilterComponent onFilter={(filter) => this.filterContent(filter)} />
-                <SceneTreeItemComponent extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} scene={scene} onRefresh={() => this.forceUpdate()} onSelectionChangedObservable={this.props.globalState.onSelectionChangedObservable} />
+                <SceneTreeItemComponent globalState={this.props.globalState} extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} scene={scene} onRefresh={() => this.forceUpdate()} onSelectionChangedObservable={this.props.globalState.onSelectionChangedObservable} />
                 <TreeItemComponent globalState={this.props.globalState} extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} items={scene.rootNodes} label="Nodes" offset={1} filter={this.state.filter} />
                 <TreeItemComponent globalState={this.props.globalState} extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} items={scene.materials} label="Materials" offset={1} filter={this.state.filter} />
                 <TreeItemComponent globalState={this.props.globalState} extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} items={textures} label="Textures" offset={1} filter={this.state.filter} />

+ 18 - 1
inspector/src/inspector.ts

@@ -404,6 +404,12 @@ export class Inspector {
                 this._CreateActionTabs(scene, options, parentControl);
             }
         }
+
+        // Light gizmos
+        scene.lights.forEach((l)=>{
+            debugger;
+            this._GlobalState.enableLightGizmo(l, true);
+        })
     }
 
     private static _CreateCanvasContainer(parentControl: HTMLElement) {
@@ -443,6 +449,17 @@ export class Inspector {
             return;
         }
 
+        // Gizmo disposal
+        this._GlobalState.lightGizmos.forEach((g)=>{
+            if(g.light){
+                this._GlobalState.enableLightGizmo(g.light, false);
+            }
+        })
+        if(this._GlobalState.gizmoManager){
+            this._GlobalState.gizmoManager.dispose();
+            this._GlobalState.gizmoManager = null;
+        }
+
         if (this._NewCanvasContainer) {
             this._DestroyCanvasContainer();
         }
@@ -489,7 +506,7 @@ export class Inspector {
                 this._EmbedHost.parentElement.removeChild(this._EmbedHost);
             }
             this._EmbedHost = null;
-        }
+        }        
 
         Inspector._OpenedPane = 0;
         this._Cleanup();

+ 1 - 1
src/Gizmos/gizmo.ts

@@ -15,7 +15,7 @@ export class Gizmo implements IDisposable {
     /**
      * The root mesh of the gizmo
      */
-    protected _rootMesh: Mesh;
+    public _rootMesh: Mesh;
     private _attachedMesh: Nullable<AbstractMesh>;
     /**
      * Ratio for the scale of the gizmo (Default: 1)

+ 2 - 1
src/Gizmos/index.ts

@@ -6,4 +6,5 @@ export * from "./gizmoManager";
 export * from "./planeRotationGizmo";
 export * from "./positionGizmo";
 export * from "./rotationGizmo";
-export * from "./scaleGizmo";
+export * from "./scaleGizmo";
+export * from "./lightGizmo";

+ 70 - 0
src/Gizmos/lightGizmo.ts

@@ -0,0 +1,70 @@
+import { Nullable } from "../types";
+import { Color3 } from "../Maths/math";
+import { AbstractMesh } from "../Meshes/abstractMesh";
+import { Mesh } from "../Meshes/mesh";
+import { Gizmo } from "./gizmo";
+import { UtilityLayerRenderer } from "../Rendering/utilityLayerRenderer";
+
+import { StandardMaterial } from '../Materials/standardMaterial';
+import { Light } from '../Lights/light';
+
+/**
+ * Gizmo that enables viewing a light
+ */
+export class LightGizmo extends Gizmo {
+    private _box: Mesh;
+
+    /**
+     * Creates a LightGizmo
+     * @param gizmoLayer The utility layer the gizmo will be added to
+     */
+    constructor(gizmoLayer?: UtilityLayerRenderer) {
+        super(gizmoLayer);
+        this._box = Mesh.CreateCylinder("light", 0.02, 0, 0.02, 16, 1, this.gizmoLayer.utilityLayerScene);
+        this._box.rotation.x = Math.PI / 2;
+        this._box.bakeCurrentTransformIntoVertices();
+        this._box.material = new StandardMaterial("", this.gizmoLayer.utilityLayerScene);
+        (this._box.material as StandardMaterial).emissiveColor = new Color3(1, 1, 1);
+        this._rootMesh.addChild(this._box);
+        this.attachedMesh = new AbstractMesh("", this.gizmoLayer.utilityLayerScene);
+    }
+    private _light: Nullable<Light> = null;
+
+    /**
+     * The light that the gizmo is attached to
+     */
+    public set light(light: Nullable<Light>) {
+        this._light = light;
+        if ((light as any).position) {
+        this.attachedMesh!.position.copyFrom((light as any).position);
+        }
+        if ((light as any).direction) {
+            this._box.setDirection((light as any).direction);
+        }
+    }
+    public get light() {
+        return this._light;
+    }
+
+    /**
+     * @hidden
+     * Updates the gizmo to match the attached mesh's position/rotation
+     */
+    protected _update() {
+        super._update();
+        if (!this._light) {
+            return;
+        }
+        if ((this._light as any).position) {
+            (this._light as any).position.copyFrom(this.attachedMesh!.position);
+        }
+        if ((this._light as any).direction) {
+            (this._light as any).direction.copyFrom(this._box.forward);
+        }
+        if (!this._light.isEnabled()) {
+            (this._box.material as StandardMaterial).emissiveColor.set(0, 0, 0);
+        }else {
+            (this._box.material as StandardMaterial).emissiveColor.set(1, 1, 1);
+        }
+    }
+}

+ 1 - 1
src/Lights/shadowLight.ts

@@ -265,7 +265,7 @@ export abstract class ShadowLight extends Light implements IShadowLight {
 
     /**
      * Sets the ShadowLight direction toward the passed target.
-     * @param target The point tot target in local space
+     * @param target The point to target in local space
      * @returns the updated ShadowLight direction
      */
     public setDirectionToTarget(target: Vector3): Vector3 {