import { StandardMaterial } from "../Materials/standardMaterial.js"; import { Color3 } from "../Maths/math.color.js"; import { Matrix, TmpVectors, Vector3 } from "../Maths/math.vector.js"; import { CreateLines } from "../Meshes/Builders/linesBuilder.js"; import { Mesh } from "../Meshes/mesh.js"; import { VertexData } from "../Meshes/mesh.vertexData.js"; import { TransformNode } from "../Meshes/transformNode.js"; /** * Class used to render a debug view of the frustum for a directional light * @see https://playground.babylonjs.com/#7EFGSG#4 * @since 5.0.0 */ export class DirectionalLightFrustumViewer { /** * Gets or sets the transparency of the frustum planes */ get transparency() { return this._transparency; } set transparency(alpha) { this._transparency = alpha; for (let i = 6; i < 12; ++i) { this._lightHelperFrustumMeshes[i].material.alpha = alpha; } } /** * true to display the edges of the frustum */ get showLines() { return this._showLines; } set showLines(show) { if (this._showLines === show) { return; } this._showLines = show; for (let i = 0; i < 6; ++i) { this._lightHelperFrustumMeshes[i].setEnabled(show); } } /** * true to display the planes of the frustum */ get showPlanes() { return this._showPlanes; } set showPlanes(show) { if (this._showPlanes === show) { return; } this._showPlanes = show; for (let i = 6; i < 12; ++i) { this._lightHelperFrustumMeshes[i].setEnabled(show); } } /** * Creates a new frustum viewer * @param light directional light to display the frustum for * @param camera camera used to retrieve the minZ / maxZ values if the shadowMinZ/shadowMaxZ values of the light are not setup */ constructor(light, camera) { this._oldPosition = new Vector3(Number.NaN, Number.NaN, Number.NaN); this._oldDirection = new Vector3(Number.NaN, Number.NaN, Number.NaN); this._transparency = 0.3; this._showLines = true; this._showPlanes = true; this._scene = light.getScene(); this._light = light; this._camera = camera; this._inverseViewMatrix = Matrix.Identity(); this._lightHelperFrustumMeshes = []; this._createGeometry(); this.show(); this.update(); } /** * Shows the frustum */ show() { this._lightHelperFrustumMeshes.forEach((mesh, index) => { mesh.setEnabled((index < 6 && this._showLines) || (index >= 6 && this._showPlanes)); }); this._oldPosition.set(Number.NaN, Number.NaN, Number.NaN); this._visible = true; } /** * Hides the frustum */ hide() { this._lightHelperFrustumMeshes.forEach((mesh) => { mesh.setEnabled(false); }); this._visible = false; } /** * Updates the frustum. * Call this method to update the frustum view if the light has changed position/direction */ update() { if (!this._visible) { return; } if (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.copyFrom(this._light.position); this._oldDirection.copyFrom(this._light.direction); this._oldAutoCalc = this._light.autoCalcShadowZBounds; this._oldMinZ = this._light.shadowMinZ; this._oldMaxZ = this._light.shadowMaxZ; TmpVectors.Vector3[0].set(this._light.orthoLeft, this._light.orthoBottom, this._light.shadowMinZ !== undefined ? this._light.shadowMinZ : this._camera.minZ); // min light extents TmpVectors.Vector3[1].set(this._light.orthoRight, this._light.orthoTop, this._light.shadowMaxZ !== undefined ? this._light.shadowMaxZ : this._camera.maxZ); // max light extents const invLightView = this._getInvertViewMatrix(); TmpVectors.Vector3[2].copyFromFloats(TmpVectors.Vector3[1].x, TmpVectors.Vector3[1].y, TmpVectors.Vector3[0].z); // n1 TmpVectors.Vector3[3].copyFromFloats(TmpVectors.Vector3[1].x, TmpVectors.Vector3[0].y, TmpVectors.Vector3[0].z); // n2 TmpVectors.Vector3[4].copyFromFloats(TmpVectors.Vector3[0].x, TmpVectors.Vector3[0].y, TmpVectors.Vector3[0].z); // n3 TmpVectors.Vector3[5].copyFromFloats(TmpVectors.Vector3[0].x, TmpVectors.Vector3[1].y, TmpVectors.Vector3[0].z); // n4 Vector3.TransformCoordinatesToRef(TmpVectors.Vector3[2], invLightView, TmpVectors.Vector3[2]); // near1 Vector3.TransformCoordinatesToRef(TmpVectors.Vector3[3], invLightView, TmpVectors.Vector3[3]); // near2 Vector3.TransformCoordinatesToRef(TmpVectors.Vector3[4], invLightView, TmpVectors.Vector3[4]); // near3 Vector3.TransformCoordinatesToRef(TmpVectors.Vector3[5], invLightView, TmpVectors.Vector3[5]); // near4 TmpVectors.Vector3[6].copyFromFloats(TmpVectors.Vector3[1].x, TmpVectors.Vector3[1].y, TmpVectors.Vector3[1].z); // f1 TmpVectors.Vector3[7].copyFromFloats(TmpVectors.Vector3[1].x, TmpVectors.Vector3[0].y, TmpVectors.Vector3[1].z); // f2 TmpVectors.Vector3[8].copyFromFloats(TmpVectors.Vector3[0].x, TmpVectors.Vector3[0].y, TmpVectors.Vector3[1].z); // f3 TmpVectors.Vector3[9].copyFromFloats(TmpVectors.Vector3[0].x, TmpVectors.Vector3[1].y, TmpVectors.Vector3[1].z); // f4 Vector3.TransformCoordinatesToRef(TmpVectors.Vector3[6], invLightView, TmpVectors.Vector3[6]); // far1 Vector3.TransformCoordinatesToRef(TmpVectors.Vector3[7], invLightView, TmpVectors.Vector3[7]); // far2 Vector3.TransformCoordinatesToRef(TmpVectors.Vector3[8], invLightView, TmpVectors.Vector3[8]); // far3 Vector3.TransformCoordinatesToRef(TmpVectors.Vector3[9], invLightView, TmpVectors.Vector3[9]); // far4 CreateLines("nearlines", { updatable: true, points: this._nearLinesPoints, instance: this._lightHelperFrustumMeshes[0] }, this._scene); CreateLines("farlines", { updatable: true, points: this._farLinesPoints, instance: this._lightHelperFrustumMeshes[1] }, this._scene); CreateLines("trlines", { updatable: true, points: this._trLinesPoints, instance: this._lightHelperFrustumMeshes[2] }, this._scene); CreateLines("brlines", { updatable: true, points: this._brLinesPoints, instance: this._lightHelperFrustumMeshes[3] }, this._scene); CreateLines("tllines", { updatable: true, points: this._tlLinesPoints, instance: this._lightHelperFrustumMeshes[4] }, this._scene); CreateLines("bllines", { updatable: true, points: this._blLinesPoints, instance: this._lightHelperFrustumMeshes[5] }, this._scene); TmpVectors.Vector3[2].toArray(this._nearPlaneVertices, 0); TmpVectors.Vector3[3].toArray(this._nearPlaneVertices, 3); TmpVectors.Vector3[4].toArray(this._nearPlaneVertices, 6); TmpVectors.Vector3[5].toArray(this._nearPlaneVertices, 9); this._lightHelperFrustumMeshes[6].geometry?.updateVerticesDataDirectly("position", this._nearPlaneVertices, 0); TmpVectors.Vector3[6].toArray(this._farPlaneVertices, 0); TmpVectors.Vector3[7].toArray(this._farPlaneVertices, 3); TmpVectors.Vector3[8].toArray(this._farPlaneVertices, 6); TmpVectors.Vector3[9].toArray(this._farPlaneVertices, 9); this._lightHelperFrustumMeshes[7].geometry?.updateVerticesDataDirectly("position", this._farPlaneVertices, 0); TmpVectors.Vector3[2].toArray(this._rightPlaneVertices, 0); TmpVectors.Vector3[6].toArray(this._rightPlaneVertices, 3); TmpVectors.Vector3[7].toArray(this._rightPlaneVertices, 6); TmpVectors.Vector3[3].toArray(this._rightPlaneVertices, 9); this._lightHelperFrustumMeshes[8].geometry?.updateVerticesDataDirectly("position", this._rightPlaneVertices, 0); TmpVectors.Vector3[5].toArray(this._leftPlaneVertices, 0); TmpVectors.Vector3[9].toArray(this._leftPlaneVertices, 3); TmpVectors.Vector3[8].toArray(this._leftPlaneVertices, 6); TmpVectors.Vector3[4].toArray(this._leftPlaneVertices, 9); this._lightHelperFrustumMeshes[9].geometry?.updateVerticesDataDirectly("position", this._leftPlaneVertices, 0); TmpVectors.Vector3[2].toArray(this._topPlaneVertices, 0); TmpVectors.Vector3[6].toArray(this._topPlaneVertices, 3); TmpVectors.Vector3[9].toArray(this._topPlaneVertices, 6); TmpVectors.Vector3[5].toArray(this._topPlaneVertices, 9); this._lightHelperFrustumMeshes[10].geometry?.updateVerticesDataDirectly("position", this._topPlaneVertices, 0); TmpVectors.Vector3[3].toArray(this._bottomPlaneVertices, 0); TmpVectors.Vector3[7].toArray(this._bottomPlaneVertices, 3); TmpVectors.Vector3[8].toArray(this._bottomPlaneVertices, 6); TmpVectors.Vector3[4].toArray(this._bottomPlaneVertices, 9); this._lightHelperFrustumMeshes[11].geometry?.updateVerticesDataDirectly("position", this._bottomPlaneVertices, 0); } /** * Dispose of the class / remove the frustum view */ dispose() { this._lightHelperFrustumMeshes.forEach((mesh) => { mesh.material?.dispose(); mesh.dispose(); }); this._rootNode.dispose(); } _createGeometry() { this._rootNode = new TransformNode("directionalLightHelperRoot_" + this._light.name, this._scene); this._rootNode.parent = this._light.parent; this._nearLinesPoints = [TmpVectors.Vector3[0], TmpVectors.Vector3[1], TmpVectors.Vector3[2], TmpVectors.Vector3[3], TmpVectors.Vector3[4]]; const nearLines = CreateLines("nearlines", { updatable: true, points: this._nearLinesPoints }, this._scene); nearLines.parent = this._rootNode; nearLines.alwaysSelectAsActiveMesh = true; this._farLinesPoints = [TmpVectors.Vector3[5], TmpVectors.Vector3[6], TmpVectors.Vector3[7], TmpVectors.Vector3[8], TmpVectors.Vector3[9]]; const farLines = CreateLines("farlines", { updatable: true, points: this._farLinesPoints }, this._scene); farLines.parent = this._rootNode; farLines.alwaysSelectAsActiveMesh = true; this._trLinesPoints = [TmpVectors.Vector3[10], TmpVectors.Vector3[11]]; const trLines = CreateLines("trlines", { updatable: true, points: this._trLinesPoints }, this._scene); trLines.parent = this._rootNode; trLines.alwaysSelectAsActiveMesh = true; this._brLinesPoints = [TmpVectors.Vector3[12], TmpVectors.Vector3[0]]; const brLines = CreateLines("brlines", { updatable: true, points: this._brLinesPoints }, this._scene); brLines.parent = this._rootNode; brLines.alwaysSelectAsActiveMesh = true; this._tlLinesPoints = [TmpVectors.Vector3[1], TmpVectors.Vector3[2]]; const tlLines = CreateLines("tllines", { updatable: true, points: this._tlLinesPoints }, this._scene); tlLines.parent = this._rootNode; tlLines.alwaysSelectAsActiveMesh = true; this._blLinesPoints = [TmpVectors.Vector3[3], TmpVectors.Vector3[4]]; const blLines = CreateLines("bllines", { updatable: true, points: this._blLinesPoints }, this._scene); blLines.parent = this._rootNode; blLines.alwaysSelectAsActiveMesh = true; this._lightHelperFrustumMeshes.push(nearLines, farLines, trLines, brLines, tlLines, blLines); const makePlane = (name, color, positions) => { const plane = new Mesh(name + "plane", this._scene); const mat = new StandardMaterial(name + "PlaneMat", this._scene); plane.material = mat; plane.parent = this._rootNode; plane.alwaysSelectAsActiveMesh = true; mat.emissiveColor = color; mat.alpha = this.transparency; 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, true); this._lightHelperFrustumMeshes.push(plane); }; this._nearPlaneVertices = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; this._farPlaneVertices = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; this._rightPlaneVertices = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; this._leftPlaneVertices = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; this._topPlaneVertices = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; this._bottomPlaneVertices = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; makePlane("near", new Color3(1, 0, 0), this._nearPlaneVertices); makePlane("far", new Color3(0.3, 0, 0), this._farPlaneVertices); makePlane("right", new Color3(0, 1, 0), this._rightPlaneVertices); makePlane("left", new Color3(0, 0.3, 0), this._leftPlaneVertices); makePlane("top", new Color3(0, 0, 1), this._topPlaneVertices); makePlane("bottom", new Color3(0, 0, 0.3), this._bottomPlaneVertices); this._nearLinesPoints[0] = TmpVectors.Vector3[2]; this._nearLinesPoints[1] = TmpVectors.Vector3[3]; this._nearLinesPoints[2] = TmpVectors.Vector3[4]; this._nearLinesPoints[3] = TmpVectors.Vector3[5]; this._nearLinesPoints[4] = TmpVectors.Vector3[2]; this._farLinesPoints[0] = TmpVectors.Vector3[6]; this._farLinesPoints[1] = TmpVectors.Vector3[7]; this._farLinesPoints[2] = TmpVectors.Vector3[8]; this._farLinesPoints[3] = TmpVectors.Vector3[9]; this._farLinesPoints[4] = TmpVectors.Vector3[6]; this._trLinesPoints[0] = TmpVectors.Vector3[2]; this._trLinesPoints[1] = TmpVectors.Vector3[6]; this._brLinesPoints[0] = TmpVectors.Vector3[3]; this._brLinesPoints[1] = TmpVectors.Vector3[7]; this._tlLinesPoints[0] = TmpVectors.Vector3[4]; this._tlLinesPoints[1] = TmpVectors.Vector3[8]; this._blLinesPoints[0] = TmpVectors.Vector3[5]; this._blLinesPoints[1] = TmpVectors.Vector3[9]; } _getInvertViewMatrix() { Matrix.LookAtLHToRef(this._light.position, this._light.position.add(this._light.direction), Vector3.UpReadOnly, this._inverseViewMatrix); this._inverseViewMatrix.invertToRef(this._inverseViewMatrix); return this._inverseViewMatrix; } } //# sourceMappingURL=directionalLightFrustumViewer.js.map