@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
302 lines (301 loc) • 16.6 kB
JavaScript
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";
import { FrameGraphUtils } from "../FrameGraph/frameGraphUtils.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 = null) {
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() {
if (this._scene.frameGraph) {
this._removeMeshesFromFrameGraph(this._scene.frameGraph);
this._addMeshesToFrameGraph(this._scene.frameGraph);
}
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() {
if (this._scene.frameGraph) {
this._removeMeshesFromFrameGraph(this._scene.frameGraph);
}
for (const mesh of this._lightHelperFrustumMeshes) {
mesh.setEnabled(false);
}
this._visible = false;
}
_addMeshesToFrameGraph(frameGraph) {
const objectRenderer = FrameGraphUtils.FindMainObjectRenderer(frameGraph);
if (objectRenderer && objectRenderer.objectList.meshes) {
for (const mesh of this._lightHelperFrustumMeshes) {
objectRenderer.objectList.meshes.push(mesh);
}
}
}
_removeMeshesFromFrameGraph(frameGraph) {
const objectRenderer = FrameGraphUtils.FindMainObjectRenderer(frameGraph);
if (objectRenderer && objectRenderer.objectList.meshes) {
for (const mesh of this._lightHelperFrustumMeshes) {
const index = objectRenderer.objectList.meshes.indexOf(mesh);
if (index !== -1) {
objectRenderer.objectList.meshes.splice(index, 1);
}
}
}
}
/**
* 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 &&
this._oldOrthoLeft === this._light.orthoLeft &&
this._oldOrthoRight === this._light.orthoRight &&
this._oldOrthoTop === this._light.orthoTop &&
this._oldOrthoBottom === this._light.orthoBottom) {
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;
this._oldOrthoLeft = this._light.orthoLeft;
this._oldOrthoRight = this._light.orthoRight;
this._oldOrthoTop = this._light.orthoTop;
this._oldOrthoBottom = this._light.orthoBottom;
TmpVectors.Vector3[0].set(this._light.orthoLeft, this._light.orthoBottom, this._light.shadowMinZ !== undefined ? this._light.shadowMinZ : (this._camera?.minZ ?? 0)); // min light extents
TmpVectors.Vector3[1].set(this._light.orthoRight, this._light.orthoTop, this._light.shadowMaxZ !== undefined ? this._light.shadowMaxZ : (this._camera?.maxZ ?? 10000)); // 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() {
if (this._scene.frameGraph) {
this._removeMeshesFromFrameGraph(this._scene.frameGraph);
}
for (const mesh of this._lightHelperFrustumMeshes) {
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