dimensions
unknown
plain_text
7 months ago
26 kB
4
Indexable
import * as BABYLON from 'babylonjs';
import { AdvancedDynamicTexture, Rectangle, Control, TextBlock } from 'babylonjs-gui';
import { Equipment } from '../models/entities/equipment';
import { MeshEnum } from '../models/enums/mesh-enum';
import { EquipmentNodeInformation } from '../models/entities/equipment-node-information';
import { DimensionPartEnum } from '../models/enums/dimension-part-enum';
import { DimensionTypeEnum } from '../models/enums/dimension-type-enum';
import { DimensionDirectionEnum } from '../models/enums/dimension-direction-enum';
interface DimensionTextCache {
mesh: BABYLON.Mesh;
texture: AdvancedDynamicTexture;
textBlock: TextBlock;
}
interface DimensionMeshSet {
mainLine: BABYLON.Mesh;
leftCorner: BABYLON.Mesh;
rightCorner: BABYLON.Mesh;
}
export class DimensionsRenderer {
private readonly DIMENSION_CORNER_SIZE = { thickness: 0.1, length: 1.5 };
private readonly DIMENSION_LINE_SIZE = { thickness: 0.1 };
private readonly DIMENSION_COLOR = new BABYLON.Color3(89 / 255, 88 / 255, 88 / 255);
private readonly RAY_LENGTH = 100;
private scene: BABYLON.Scene;
private scale: number;
private dimensionMaterial: BABYLON.StandardMaterial;
private dimensionNodes: Map<DimensionDirectionEnum, BABYLON.TransformNode> = new Map();
private dimensionMeshes: Map<string, DimensionMeshSet> = new Map();
private dimensionTexts: Map<string, DimensionTextCache> = new Map();
private readonly rayDirections = {
back: new BABYLON.Vector3(0, 0, -1),
right: new BABYLON.Vector3(1, 0, 0),
forward: new BABYLON.Vector3(0, 0, 1),
left: new BABYLON.Vector3(-1, 0, 0),
};
constructor(scene: BABYLON.Scene, scale: number) {
this.scene = scene;
this.scale = scale;
this.dimensionMaterial = new BABYLON.StandardMaterial('dimension-material', this.scene);
this.dimensionMaterial.diffuseColor = this.DIMENSION_COLOR;
this.dimensionMaterial.emissiveColor = this.DIMENSION_COLOR;
this.dimensionMaterial.disableLighting = true;
}
disposeDimensions(): void {
this.dimensionTexts.forEach((cache) => {
if (cache.texture) {
cache.texture.dispose();
}
if (cache.mesh) {
cache.mesh.dispose();
}
});
this.dimensionTexts.clear();
this.dimensionMeshes.forEach((meshSet) => {
if (meshSet.mainLine) meshSet.mainLine.dispose();
if (meshSet.leftCorner) meshSet.leftCorner.dispose();
if (meshSet.rightCorner) meshSet.rightCorner.dispose();
});
this.dimensionMeshes.clear();
this.dimensionNodes.forEach((node) => {
if (node) {
node.dispose();
}
});
this.dimensionNodes.clear();
Object.values(DimensionDirectionEnum).forEach((direction) => {
const transformNode = this.scene.getTransformNodeByName(
`${direction}-${MeshEnum.DIMENSION}-${DimensionPartEnum.PARENT_NODE}`,
);
if (transformNode) {
const children = transformNode.getChildMeshes();
children.forEach((child) => {
child.dispose();
});
transformNode.dispose();
}
});
const textBoxes = this.scene.getMeshesByID(MeshEnum.DIMENSION_TEXT);
textBoxes.forEach((textBox: BABYLON.AbstractMesh) => {
textBox.dispose();
});
Object.values(DimensionTypeEnum).forEach((dimensionType) => {
const dimensionLines = this.scene.getMeshesByID(
`${dimensionType}-${MeshEnum.DIMENSION}-${DimensionPartEnum.MAIN_LINE}`,
);
const leftCorners = this.scene.getMeshesByID(
`${dimensionType}-${MeshEnum.DIMENSION}-${DimensionPartEnum.LEFT_CORNER}`,
);
const rightCorners = this.scene.getMeshesByID(
`${dimensionType}-${MeshEnum.DIMENSION}-${DimensionPartEnum.RIGHT_CORNER}`,
);
[...dimensionLines, ...leftCorners, ...rightCorners].forEach((mesh) => {
if (mesh) mesh.dispose();
});
});
}
getTransformNodeSize(equipmentNodeInformation: EquipmentNodeInformation): BABYLON.Vector3 {
const width = equipmentNodeInformation.width / this.scale;
const length = equipmentNodeInformation.length / this.scale;
const height = equipmentNodeInformation.height / this.scale;
const rotationRad = BABYLON.Tools.ToRadians(equipmentNodeInformation.rotation);
const cosTheta = Math.abs(Math.cos(rotationRad));
const sinTheta = Math.abs(Math.sin(rotationRad));
// calculate rotated dimensions
const size = new BABYLON.Vector3(
width * cosTheta + length * sinTheta,
height,
width * sinTheta + length * cosTheta,
);
return size;
}
drawDimensions(tn: BABYLON.TransformNode, equipment: Equipment): void {
this.scene.blockMaterialDirtyMechanism = true;
// force update of transform node and its children
tn.computeWorldMatrix(true);
tn.getChildMeshes().forEach((mesh) => mesh.computeWorldMatrix(true));
const size = this.getTransformNodeSize(equipment.nodeInformation);
const elevation = equipment.nodeInformation.elevation / this.scale;
const height = equipment.nodeInformation.height / this.scale;
const tnPosition = tn.position.clone();
// cast rays in all four directions to find the nearest walls
const dimensionResults = this.castRaysForDimensions(tnPosition, size, height);
if (dimensionResults.back.distance > 0.1) {
this.updateOrCreateDimension(
DimensionDirectionEnum.BACK,
dimensionResults.back.position,
{ width: 0, height: 0, length: dimensionResults.back.distance },
DimensionTypeEnum.LENGTH,
);
} else {
this.hideDimension(DimensionDirectionEnum.BACK);
}
if (dimensionResults.right.distance > 0.1) {
this.updateOrCreateDimension(
DimensionDirectionEnum.RIGHT,
dimensionResults.right.position,
{ width: dimensionResults.right.distance, height: 0, length: 0 },
DimensionTypeEnum.WIDTH,
);
} else {
this.hideDimension(DimensionDirectionEnum.RIGHT);
}
if (dimensionResults.forward.distance > 0.1) {
this.updateOrCreateDimension(
DimensionDirectionEnum.FORWARD,
dimensionResults.forward.position,
{ width: 0, height: 0, length: dimensionResults.forward.distance },
DimensionTypeEnum.LENGTH,
);
} else {
this.hideDimension(DimensionDirectionEnum.FORWARD);
}
if (dimensionResults.left.distance > 0.1) {
this.updateOrCreateDimension(
DimensionDirectionEnum.LEFT,
dimensionResults.left.position,
{ width: dimensionResults.left.distance, height: 0, length: 0 },
DimensionTypeEnum.WIDTH,
);
} else {
this.hideDimension(DimensionDirectionEnum.LEFT);
}
// ground dimension
if (elevation > 0.1) {
const startGround = tnPosition.clone();
startGround.y += elevation;
this.updateOrCreateDimension(
DimensionDirectionEnum.GROUND,
startGround,
{ width: 0, height: elevation, length: 0 },
DimensionTypeEnum.HEIGHT,
);
} else {
this.hideDimension(DimensionDirectionEnum.GROUND);
}
// Restore material dirty mechanism after batch operations
this.scene.blockMaterialDirtyMechanism = false;
}
private hideDimension(direction: DimensionDirectionEnum): void {
const transformNode = this.dimensionNodes.get(direction);
if (transformNode) {
transformNode.setEnabled(false);
}
}
private castRaysForDimensions(
position: BABYLON.Vector3,
size: BABYLON.Vector3,
height: number,
): {
back: { distance: number; position: BABYLON.Vector3 };
right: { distance: number; position: BABYLON.Vector3 };
forward: { distance: number; position: BABYLON.Vector3 };
left: { distance: number; position: BABYLON.Vector3 };
} {
// create predicate to exclude non-wall meshes
const predicate = (mesh: BABYLON.AbstractMesh) => {
return mesh.id === MeshEnum.WALL || mesh.id === MeshEnum.INNER_WALL;
};
const startBack = position.clone();
startBack.z -= size.z / 2;
startBack.y = position.y - height / 2;
const startRight = position.clone();
startRight.x += size.x / 2;
startRight.y = position.y - height / 2;
const startForward = position.clone();
startForward.z += size.z / 2;
startForward.y = position.y - height / 2;
const startLeft = position.clone();
startLeft.x -= size.x / 2;
startLeft.y = position.y - height / 2;
const backRay = new BABYLON.Ray(startBack, new BABYLON.Vector3(0, 0, -1), this.RAY_LENGTH);
const rightRay = new BABYLON.Ray(startRight, new BABYLON.Vector3(1, 0, 0), this.RAY_LENGTH);
const forwardRay = new BABYLON.Ray(startForward, new BABYLON.Vector3(0, 0, 1), this.RAY_LENGTH);
const leftRay = new BABYLON.Ray(startLeft, new BABYLON.Vector3(-1, 0, 0), this.RAY_LENGTH);
const backHit = this.scene.pickWithRay(backRay, predicate);
const rightHit = this.scene.pickWithRay(rightRay, predicate);
const forwardHit = this.scene.pickWithRay(forwardRay, predicate);
const leftHit = this.scene.pickWithRay(leftRay, predicate);
// calculate dimensions based on ray hits
const backDistance = backHit && backHit.hit ? backHit.distance : 0;
const rightDistance = rightHit && rightHit.hit ? rightHit.distance : 0;
const forwardDistance = forwardHit && forwardHit.hit ? forwardHit.distance : 0;
const leftDistance = leftHit && leftHit.hit ? leftHit.distance : 0;
// calculate dimension positions
const backPosition = startBack.clone();
if (backDistance > 0) {
backPosition.z -= backDistance / 2;
}
const rightPosition = startRight.clone();
if (rightDistance > 0) {
rightPosition.x += rightDistance / 2;
}
const forwardPosition = startForward.clone();
if (forwardDistance > 0) {
forwardPosition.z += forwardDistance / 2;
}
const leftPosition = startLeft.clone();
if (leftDistance > 0) {
leftPosition.x -= leftDistance / 2;
}
return {
back: { distance: backDistance, position: backPosition },
right: { distance: rightDistance, position: rightPosition },
forward: { distance: forwardDistance, position: forwardPosition },
left: { distance: leftDistance, position: leftPosition },
};
}
private updateOrCreateDimension(
direction: DimensionDirectionEnum,
position: BABYLON.Vector3,
dimensions: { width: number; height: number; length: number; scale?: BABYLON.Vector3 },
type: DimensionTypeEnum,
): void {
const transformNodeName = `${direction}-${MeshEnum.DIMENSION}-${DimensionPartEnum.PARENT_NODE}`;
let transformNode = this.dimensionNodes.get(direction);
if (!transformNode) {
transformNode =
this.scene.getTransformNodeByName(transformNodeName) ||
new BABYLON.TransformNode(transformNodeName, this.scene);
transformNode.id = transformNodeName;
this.dimensionNodes.set(direction, transformNode);
}
transformNode.setEnabled(true);
let text: number = 0;
let dimensionSize: { width: number; height: number; depth: number } = { width: 0, height: 0, depth: 0 };
let cornerSize: { width: number; height: number; depth: number } = { width: 0, height: 0, depth: 0 };
let textPosition = new BABYLON.Vector3();
let dimensionLeftCornerPosition = new BABYLON.Vector3();
let dimensionRightCornerPosition = new BABYLON.Vector3();
switch (type) {
case DimensionTypeEnum.WIDTH:
dimensionSize = {
width: 1, // for scaling
height: this.DIMENSION_LINE_SIZE.thickness,
depth: this.DIMENSION_CORNER_SIZE.thickness,
};
dimensionLeftCornerPosition = new BABYLON.Vector3(
position.x - dimensions.width / 2,
position.y,
position.z,
);
dimensionRightCornerPosition = new BABYLON.Vector3(
position.x + dimensions.width / 2,
position.y,
position.z,
);
cornerSize = {
width: this.DIMENSION_CORNER_SIZE.thickness,
height: this.DIMENSION_CORNER_SIZE.length,
depth: this.DIMENSION_CORNER_SIZE.thickness,
};
textPosition = new BABYLON.Vector3(position.x, position.y, position.z);
text = dimensions.width;
break;
case DimensionTypeEnum.LENGTH:
dimensionLeftCornerPosition = new BABYLON.Vector3(
position.x,
position.y,
position.z - dimensions.length / 2,
);
dimensionRightCornerPosition = new BABYLON.Vector3(
position.x,
position.y,
position.z + dimensions.length / 2,
);
dimensionSize = {
width: this.DIMENSION_CORNER_SIZE.thickness,
height: this.DIMENSION_LINE_SIZE.thickness,
depth: 1,
};
cornerSize = {
width: this.DIMENSION_CORNER_SIZE.thickness,
height: this.DIMENSION_CORNER_SIZE.length,
depth: this.DIMENSION_CORNER_SIZE.thickness,
};
textPosition = new BABYLON.Vector3(position.x, position.y, position.z);
text = dimensions.length;
break;
case DimensionTypeEnum.HEIGHT:
const lineHeight = dimensions.height;
const linePosition = position.clone();
linePosition.y = position.y - lineHeight / 2;
dimensionLeftCornerPosition = new BABYLON.Vector3(position.x, position.y - lineHeight, position.z);
dimensionRightCornerPosition = new BABYLON.Vector3(position.x, position.y, position.z);
dimensionSize = {
width: this.DIMENSION_CORNER_SIZE.thickness,
height: 1,
depth: this.DIMENSION_CORNER_SIZE.thickness,
};
cornerSize = {
width: this.DIMENSION_CORNER_SIZE.length,
height: this.DIMENSION_CORNER_SIZE.thickness,
depth: this.DIMENSION_CORNER_SIZE.thickness,
};
textPosition = new BABYLON.Vector3(position.x + 1, position.y - lineHeight / 2, position.z);
text = dimensions.height;
position = linePosition;
break;
}
if (text <= 0) {
return;
}
const leftCornerName = `${type}-${MeshEnum.DIMENSION}-${DimensionPartEnum.LEFT_CORNER}-${direction}`;
const rightCornerName = `${type}-${MeshEnum.DIMENSION}-${DimensionPartEnum.RIGHT_CORNER}-${direction}`;
const mainLineName = `${type}-${MeshEnum.DIMENSION}-${DimensionPartEnum.MAIN_LINE}-${direction}`;
const meshSetKey = `${type}-${MeshEnum.DIMENSION}-${direction}`;
let meshSet = this.dimensionMeshes.get(meshSetKey);
if (!meshSet) {
const mainLine = BABYLON.MeshBuilder.CreateBox(mainLineName, dimensionSize, this.scene);
const leftCorner = BABYLON.MeshBuilder.CreateBox(leftCornerName, cornerSize, this.scene);
const rightCorner = BABYLON.MeshBuilder.CreateBox(rightCornerName, cornerSize, this.scene);
[mainLine, leftCorner, rightCorner].forEach((mesh) => {
if (mesh) {
mesh.material = this.dimensionMaterial;
mesh.isPickable = false;
mesh.parent = transformNode;
}
});
mainLine.id = mainLineName;
leftCorner.id = leftCornerName;
rightCorner.id = rightCornerName;
// initial scaling based on dimension type
switch (type) {
case DimensionTypeEnum.WIDTH:
mainLine.scaling.x = dimensions.width;
break;
case DimensionTypeEnum.LENGTH:
mainLine.scaling.z = dimensions.length;
break;
case DimensionTypeEnum.HEIGHT:
mainLine.scaling.y = dimensions.height;
break;
}
meshSet = {
mainLine,
leftCorner,
rightCorner,
};
this.dimensionMeshes.set(meshSetKey, meshSet);
} else {
if (meshSet.mainLine) {
switch (type) {
case DimensionTypeEnum.WIDTH:
meshSet.mainLine.scaling.x = dimensions.width;
break;
case DimensionTypeEnum.LENGTH:
meshSet.mainLine.scaling.z = dimensions.length;
break;
case DimensionTypeEnum.HEIGHT:
meshSet.mainLine.scaling.y = dimensions.height;
break;
}
}
}
meshSet.mainLine.position = position.clone();
meshSet.leftCorner.position = dimensionLeftCornerPosition.clone();
meshSet.rightCorner.position = dimensionRightCornerPosition.clone();
this.updateOrCreateDimensionText(meshSet.mainLine, Math.round(text * 100), textPosition);
}
private updateOrCreateDimensionText(mesh: BABYLON.Mesh, inputText: number, position?: BABYLON.Vector3): void {
const textBoxName = `${MeshEnum.DIMENSION_TEXT}-${mesh.name}`;
let textCache = this.dimensionTexts.get(textBoxName);
if (!textCache) {
const textBox = BABYLON.MeshBuilder.CreatePlane(textBoxName, { width: 3.5, height: 1.25 }, this.scene);
textBox.id = MeshEnum.DIMENSION_TEXT;
textBox.billboardMode = BABYLON.Mesh.BILLBOARDMODE_ALL;
textBox.renderingGroupId = 1;
const label = new Rectangle(`label for ${mesh.name}`);
label.background = 'black';
label.height = '100%';
label.width = '100%';
label.cornerRadius = 4;
label.thickness = 0;
label.useBitmapCache = true;
const input = new TextBlock('text-input');
input.width = '100%';
input.height = '100%';
input.text = inputText.toString();
input.color = 'white';
input.fontFamily = 'Poppins';
input.fontSize = '70%';
input.fontWeight = 'bold';
input.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
input.verticalAlignment = Control.VERTICAL_ALIGNMENT_CENTER;
const advancedTexture = AdvancedDynamicTexture.CreateForMesh(textBox, 96, 32, false, undefined, !position);
advancedTexture.name = `${mesh.name}-textbox-texture`;
advancedTexture.addControl(label);
label.addControl(input);
textCache = {
mesh: textBox,
texture: advancedTexture,
textBlock: input,
};
this.dimensionTexts.set(textBoxName, textCache);
} else {
textCache.textBlock.text = inputText.toString();
textCache.mesh.setEnabled(true);
}
if (position) {
textCache.mesh.position.copyFrom(position);
} else {
textCache.mesh.position.copyFrom(mesh.position);
}
}
updateDimensionsPositions(tn: BABYLON.TransformNode, equipment: Equipment): void {
this.scene.blockMaterialDirtyMechanism = true;
const size = this.getTransformNodeSize(equipment.nodeInformation);
const elevation = equipment.nodeInformation.elevation / this.scale;
const height = equipment.nodeInformation.height / this.scale;
const tnPosition = tn.position;
const dimensionResults = this.castRaysForDimensions(tnPosition, size, height);
if (dimensionResults.back.distance > 0.1) {
this.updateDimensionPosition(
DimensionDirectionEnum.BACK,
dimensionResults.back.position,
dimensionResults.back.distance,
DimensionTypeEnum.LENGTH,
);
} else {
this.hideDimension(DimensionDirectionEnum.BACK);
}
if (dimensionResults.right.distance > 0.1) {
this.updateDimensionPosition(
DimensionDirectionEnum.RIGHT,
dimensionResults.right.position,
dimensionResults.right.distance,
DimensionTypeEnum.WIDTH,
);
} else {
this.hideDimension(DimensionDirectionEnum.RIGHT);
}
if (dimensionResults.forward.distance > 0.1) {
this.updateDimensionPosition(
DimensionDirectionEnum.FORWARD,
dimensionResults.forward.position,
dimensionResults.forward.distance,
DimensionTypeEnum.LENGTH,
);
} else {
this.hideDimension(DimensionDirectionEnum.FORWARD);
}
if (dimensionResults.left.distance > 0.1) {
this.updateDimensionPosition(
DimensionDirectionEnum.LEFT,
dimensionResults.left.position,
dimensionResults.left.distance,
DimensionTypeEnum.WIDTH,
);
} else {
this.hideDimension(DimensionDirectionEnum.LEFT);
}
if (elevation > 0.1) {
const startGround = tnPosition.clone();
startGround.y += elevation;
this.updateDimensionPosition(
DimensionDirectionEnum.GROUND,
startGround,
elevation,
DimensionTypeEnum.HEIGHT,
);
} else {
this.hideDimension(DimensionDirectionEnum.GROUND);
}
this.scene.blockMaterialDirtyMechanism = false;
}
private updateDimensionPosition(
direction: DimensionDirectionEnum,
position: BABYLON.Vector3,
distance: number,
type: DimensionTypeEnum,
): void {
const transformNode = this.dimensionNodes.get(direction);
if (!transformNode) return;
transformNode.setEnabled(true);
const meshSetKey = `${type}-${MeshEnum.DIMENSION}-${direction}`;
const meshSet = this.dimensionMeshes.get(meshSetKey);
if (!meshSet) return;
// update positions and scaling based on dimension type
switch (type) {
case DimensionTypeEnum.WIDTH:
meshSet.mainLine.position.copyFromFloats(position.x, position.y, position.z);
meshSet.leftCorner.position.copyFromFloats(position.x - distance / 2, position.y, position.z);
meshSet.rightCorner.position.copyFromFloats(position.x + distance / 2, position.y, position.z);
meshSet.mainLine.scaling.x = distance;
break;
case DimensionTypeEnum.LENGTH:
meshSet.mainLine.position.copyFromFloats(position.x, position.y, position.z);
meshSet.leftCorner.position.copyFromFloats(position.x, position.y, position.z - distance / 2);
meshSet.rightCorner.position.copyFromFloats(position.x, position.y, position.z + distance / 2);
meshSet.mainLine.scaling.z = distance;
break;
case DimensionTypeEnum.HEIGHT:
meshSet.mainLine.position.copyFromFloats(position.x, position.y - distance / 2, position.z);
meshSet.leftCorner.position.copyFromFloats(position.x, position.y - distance, position.z);
meshSet.rightCorner.position.copyFromFloats(position.x, position.y, position.z);
meshSet.mainLine.scaling.y = distance;
break;
}
const textCache = this.dimensionTexts.get(`${MeshEnum.DIMENSION_TEXT}-${meshSet.mainLine.name}`);
if (textCache) {
textCache.textBlock.text = Math.round(distance * 100).toString();
if (type === DimensionTypeEnum.HEIGHT) {
textCache.mesh.position.copyFromFloats(position.x + 1, position.y - distance / 2, position.z);
} else {
textCache.mesh.position.copyFromFloats(position.x, position.y, position.z);
}
}
}
}
Editor is loading...
Leave a Comment