Parcourir la source

Add a debug frustum display for directional lights

Popov72 il y a 4 ans
Parent
commit
07159266bf

+ 24 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/lights/directionalLightPropertyGridComponent.tsx

@@ -12,6 +12,7 @@ import { GlobalState } from '../../../../globalState';
 import { CheckBoxLineComponent } from "../../../../../sharedUiComponents/lines/checkBoxLineComponent"
 import { ShadowGenerator } from 'babylonjs/Lights/Shadows/shadowGenerator';
 import { CascadedShadowGenerator } from 'babylonjs/Lights/Shadows/cascadedShadowGenerator';
+import { DirectionalLightHelper } from "../../tools/directionalLightHelper";
 
 interface IDirectionalLightPropertyGridComponentProps {
     globalState: GlobalState,
@@ -25,12 +26,32 @@ export class DirectionalLightPropertyGridComponent extends React.Component<IDire
         super(props);
     }
 
+    displayFrustum() {
+        const light = this.props.light;
+        const camera = light.getScene().activeCamera;
+
+        let displayFrustum = (light as any)._displayFrustum = !(!!(light as any)._displayFrustum);
+
+        if ((light as any)._displayFrustumObservable) {
+            (light as any)._displayFrustumDLH.hide();
+            light.getScene().onAfterRenderObservable.remove((light as any)._displayFrustumObservable);
+        }
+
+        if (displayFrustum && camera) {
+            const dlh = (light as any)._displayFrustumDLH = new DirectionalLightHelper(light, camera);
+            (light as any)._displayFrustumObservable = light.getScene().onAfterRenderObservable.add(() => {
+                dlh.show();
+            });
+        }
+    }
+
     render() {
         const light = this.props.light;
 
         const generator = light.getShadowGenerator() as (ShadowGenerator | CascadedShadowGenerator) || null;
 
         const hideAutoCalcShadowZBounds = generator instanceof CascadedShadowGenerator;
+        const displayFrustum = (light as any)._displayFrustum ?? false;;
 
         return (
             <div className="pane">
@@ -45,6 +66,9 @@ export class DirectionalLightPropertyGridComponent extends React.Component<IDire
                     }
                 </LineContainerComponent>
                 <CommonShadowLightPropertyGridComponent globalState={this.props.globalState} lockObject={this.props.lockObject} light={light} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                <LineContainerComponent title="DEBUG" closed={true}>
+                    <CheckBoxLineComponent label="Display frustum" isSelected={() => displayFrustum} onSelect={() => this.displayFrustum()} />
+                </LineContainerComponent>
             </div>
         );
     }

+ 165 - 0
inspector/src/components/actionTabs/tabs/tools/directionalLightHelper.ts

@@ -0,0 +1,165 @@
+import { TransformNode } from "babylonjs";
+import { Camera } from "babylonjs/Cameras/camera";
+import { DirectionalLight } from "babylonjs/Lights/directionalLight";
+import { StandardMaterial } from "babylonjs/Materials/standardMaterial";
+import { Color3, Matrix, Vector3 } from "babylonjs/Maths/math";
+import { Mesh } from "babylonjs/Meshes/mesh";
+import { VertexData } from "babylonjs/Meshes/mesh.vertexData";
+import { MeshBuilder } from "babylonjs/Meshes/meshBuilder";
+import { Scene } from "babylonjs/scene";
+
+export class DirectionalLightHelper {
+
+    private _scene: Scene;
+    private _light: DirectionalLight;
+    private _camera: Camera;
+    private _viewMatrix: Matrix;
+    private _lightHelperFrustumLines: TransformNode[];
+    private _oldPosition: Vector3;
+    private _oldDirection: Vector3;
+    private _oldAutoCalc: boolean;
+    private _oldMinZ: number;
+    private _oldMaxZ: number;
+
+    constructor(light: DirectionalLight, camera: Camera) {
+        this._scene = light.getScene();
+        this._light = light;
+        this._camera = camera;
+        this._viewMatrix = Matrix.Identity();
+        this._lightHelperFrustumLines = [];
+    }
+
+    public hide() {
+        this._hide();
+        this._oldPosition = null as any;
+    }
+
+    public show() {
+        if (this._oldPosition 
+            && this._oldPosition.equals(this._light.position) 
+            && this._oldDirection.equals(this._light.direction) 
+            && this._oldAutoCalc === this._light.autoCalcShadowZBounds
+            && this._oldMinZ === this._light.shadowMinZ
+            && this._oldMaxZ === this._light.shadowMaxZ
+        ) {
+            return;
+        }
+
+        this._oldPosition = this._light.position.clone();
+        this._oldDirection = this._light.direction.clone();
+        this._oldAutoCalc = this._light.autoCalcShadowZBounds;
+        this._oldMinZ = this._light.shadowMinZ;
+        this._oldMaxZ = this._light.shadowMaxZ;
+
+        this._hide();
+
+        const lightExtents = this._getLightExtents();
+        const lightView = this._getViewMatrix();
+
+        if (!lightExtents || !lightView) {
+            return;
+        }
+
+        const invLightView = Matrix.Invert(lightView);
+
+        const n1 = new Vector3(lightExtents.max.x, lightExtents.max.y, lightExtents.min.z);
+        const n2 = new Vector3(lightExtents.max.x, lightExtents.min.y, lightExtents.min.z);
+        const n3 = new Vector3(lightExtents.min.x, lightExtents.min.y, lightExtents.min.z);
+        const n4 = new Vector3(lightExtents.min.x, lightExtents.max.y, lightExtents.min.z);
+
+        const near1 = Vector3.TransformCoordinates(n1, invLightView);
+        const near2 = Vector3.TransformCoordinates(n2, invLightView);
+        const near3 = Vector3.TransformCoordinates(n3, invLightView);
+        const near4 = Vector3.TransformCoordinates(n4, invLightView);
+
+        const f1 = new Vector3(lightExtents.max.x, lightExtents.max.y, lightExtents.max.z);
+        const f2 = new Vector3(lightExtents.max.x, lightExtents.min.y, lightExtents.max.z);
+        const f3 = new Vector3(lightExtents.min.x, lightExtents.min.y, lightExtents.max.z);
+        const f4 = new Vector3(lightExtents.min.x, lightExtents.max.y, lightExtents.max.z);
+
+        const far1 = Vector3.TransformCoordinates(f1, invLightView);
+        const far2 = Vector3.TransformCoordinates(f2, invLightView);
+        const far3 = Vector3.TransformCoordinates(f3, invLightView);
+        const far4 = Vector3.TransformCoordinates(f4, invLightView);
+
+        const rootNode = new TransformNode("directionalLightHelperRoot_" + this._light.name, this._scene);
+        rootNode.parent = this._light;
+
+        const nearLines = MeshBuilder.CreateLines("nearlines", { points: [near1, near2, near3, near4, near1] }, this._scene);
+        nearLines.parent = rootNode;
+
+        const farLines = MeshBuilder.CreateLines("farlines",  { points: [far1, far2, far3, far4, far1] }, this._scene);
+        farLines.parent = rootNode;
+
+        const trLines = MeshBuilder.CreateLines("trlines", { points: [ near1, far1 ] }, this._scene);
+        trLines.parent = rootNode;
+
+        const brLines = MeshBuilder.CreateLines("brlines", { points: [ near2, far2 ] }, this._scene);
+        brLines.parent = rootNode;
+
+        const tlLines = MeshBuilder.CreateLines("tllines", { points: [ near3, far3 ] }, this._scene);
+        tlLines.parent = rootNode;
+
+        const blLines = MeshBuilder.CreateLines("bllines", { points: [ near4, far4 ] }, this._scene);
+        blLines.parent = rootNode;
+
+        this._lightHelperFrustumLines.push(rootNode, nearLines, farLines, trLines, brLines, tlLines, blLines);
+
+        const makePlane = (name: string, color: Color3, positions: number[]) => {
+            const plane = new Mesh(name + "plane", this._scene);
+            const mat = new StandardMaterial(name + "PlaneMat", this._scene);
+
+            plane.material = mat;
+            plane.parent = rootNode;
+
+            mat.emissiveColor = color;
+            mat.alpha = 0.3;
+            mat.backFaceCulling = false;
+            mat.disableLighting = true;
+
+            const indices = [0, 1, 2, 0, 2, 3];
+
+            const vertexData = new VertexData();
+
+            vertexData.positions = positions;
+            vertexData.indices = indices;
+
+            vertexData.applyToMesh(plane);
+
+            this._lightHelperFrustumLines.push(plane);
+        };
+
+        makePlane("near",   new Color3(1, 0, 0),    [near1.x, near1.y, near1.z, near2.x, near2.y, near2.z, near3.x, near3.y, near3.z, near4.x, near4.y, near4.z ]);
+        makePlane("far",    new Color3(0.3, 0, 0),  [far1.x, far1.y, far1.z, far2.x, far2.y, far2.z, far3.x, far3.y, far3.z, far4.x, far4.y, far4.z ]);
+        makePlane("right",  new Color3(0, 1, 0),    [near1.x, near1.y, near1.z, far1.x, far1.y, far1.z, far2.x, far2.y, far2.z, near2.x, near2.y, near2.z ]);
+        makePlane("left",   new Color3(0, 0.3, 0),  [near4.x, near4.y, near4.z, far4.x, far4.y, far4.z, far3.x, far3.y, far3.z, near3.x, near3.y, near3.z ]);
+        makePlane("top",    new Color3(0, 0, 1),    [near1.x, near1.y, near1.z, far1.x, far1.y, far1.z, far4.x, far4.y, far4.z, near4.x, near4.y, near4.z ]);
+        makePlane("bottom", new Color3(0, 0, 0.3),  [near2.x, near2.y, near2.z, far2.x, far2.y, far2.z, far3.x, far3.y, far3.z, near3.x, near3.y, near3.z ]);
+    }
+
+    protected _hide() {
+        this._lightHelperFrustumLines.forEach((t) => {
+            if (t instanceof Mesh) {
+                t.material?.dispose();
+            }
+            t.dispose();
+        });
+
+        this._lightHelperFrustumLines.length = 0;
+    }
+
+    protected _getLightExtents() {
+        const light = this._light;
+
+        return {
+            "min": new Vector3(light.orthoLeft, light.orthoBottom, light.shadowMinZ !== undefined ? light.shadowMinZ : this._camera.minZ),
+            "max": new Vector3(light.orthoRight, light.orthoTop, light.shadowMaxZ !== undefined ? light.shadowMaxZ : this._camera.maxZ)
+        };
+    }
+
+    protected _getViewMatrix() {
+        // same computation here than in the shadow generator
+        Matrix.LookAtLHToRef(this._light.position, this._light.position.add(this._light.direction), Vector3.Up(), this._viewMatrix);
+        return this._viewMatrix;
+    }
+}