dimensions
unknown
plain_text
a month ago
26 kB
3
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