From b9f3d56cc215545a5974c4de87dff9b41eb57b05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Gonz=C3=A1lez=20Viegas?= Date: Sun, 3 Mar 2024 20:02:20 +0100 Subject: [PATCH] feat: define boolean operations at the element level --- resources/openbim-clay.js | 316 +++++++++--------- src/base/clay-object.ts | 59 ++++ src/base/ifc-getter.ts | 45 --- src/base/model.ts | 10 + src/families/Family/index.ts | 66 +++- .../Furniture/SimpleFurniture/index.ts | 12 +- src/families/Opening/index.ts | 45 +-- src/families/Slabs/SimpleSlab/index.ts | 11 +- src/families/Walls/SimpleWall/index.html | 27 +- src/families/Walls/SimpleWall/index.ts | 66 ++-- src/geometries/Brep/index.ts | 8 +- src/geometries/Extrusion/index.ts | 62 +--- src/geometries/Geometry/index.ts | 60 ---- .../Profiles/RectangleProfile/index.ts | 26 +- src/utils/ifc-utils.ts | 76 +++++ src/utils/math-utils.ts | 13 + 16 files changed, 473 insertions(+), 429 deletions(-) delete mode 100644 src/base/ifc-getter.ts create mode 100644 src/utils/ifc-utils.ts create mode 100644 src/utils/math-utils.ts diff --git a/resources/openbim-clay.js b/resources/openbim-clay.js index 701d0d5..260029d 100644 --- a/resources/openbim-clay.js +++ b/resources/openbim-clay.js @@ -77662,6 +77662,49 @@ class ClayObject { constructor(model) { this.model = model; } + setMesh(id, mesh) { + const modelID = this.model.modelID; + this.model.ifcAPI.StreamMeshes(modelID, [id], (ifcMesh) => { + mesh.geometry.dispose(); + const { geometryExpressID, flatTransformation } = ifcMesh.geometries.get(0); + const data = this.model.ifcAPI.GetGeometry(modelID, geometryExpressID); + mesh.geometry = this.ifcToThreeGeometry(data); + const matrix = new THREE.Matrix4().fromArray(flatTransformation); + mesh.position.set(0, 0, 0); + mesh.rotation.set(0, 0, 0); + mesh.scale.set(1, 1, 1); + mesh.updateMatrix(); + mesh.applyMatrix4(matrix); + }); + } + newThreeMesh() { + const geometry = new THREE.BufferGeometry(); + const mesh = new THREE.InstancedMesh(geometry, this.model.material, 1); + mesh.frustumCulled = false; + const identity = new THREE.Matrix4().identity(); + mesh.setMatrixAt(0, identity); + mesh.instanceMatrix.needsUpdate = true; + return mesh; + } + ifcToThreeGeometry(data) { + const index = this.model.ifcAPI.GetIndexArray(data.GetIndexData(), data.GetIndexDataSize()); + const vertexData = this.model.ifcAPI.GetVertexArray(data.GetVertexData(), data.GetVertexDataSize()); + const position = new Float32Array(vertexData.length / 2); + const normal = new Float32Array(vertexData.length / 2); + for (let i = 0; i < vertexData.length; i += 6) { + position[i / 2] = vertexData[i]; + position[i / 2 + 1] = vertexData[i + 1]; + position[i / 2 + 2] = vertexData[i + 2]; + normal[i / 2] = vertexData[i + 3]; + normal[i / 2 + 1] = vertexData[i + 4]; + normal[i / 2 + 2] = vertexData[i + 5]; + } + const geometry = new THREE.BufferGeometry(); + geometry.setAttribute("position", new THREE.BufferAttribute(position, 3)); + geometry.setAttribute("normal", new THREE.BufferAttribute(normal, 3)); + geometry.setIndex(Array.from(index)); + return geometry; + } } class Model { @@ -77705,6 +77748,15 @@ class Model { } return item; } + update() { + if (this._modelID === undefined) { + throw new Error("Malformed model!"); + } + const model = this.ifcAPI.SaveModel(this._modelID); + this.ifcAPI.CloseModel(this._modelID); + this._modelID++; + this.ifcAPI.OpenModel(model); + } } class ClayGeometry extends ClayObject { @@ -77714,30 +77766,6 @@ class ClayGeometry extends ClayObject { this.lastClipping = null; this.clippings = new Map(); } - setMesh(id, mesh) { - const modelID = this.model.modelID; - this.model.ifcAPI.StreamMeshes(modelID, [id], (ifcMesh) => { - mesh.geometry.dispose(); - const { geometryExpressID, flatTransformation } = ifcMesh.geometries.get(0); - const data = this.model.ifcAPI.GetGeometry(modelID, geometryExpressID); - mesh.geometry = this.ifcToThreeGeometry(data); - const matrix = new THREE.Matrix4().fromArray(flatTransformation); - mesh.position.set(0, 0, 0); - mesh.rotation.set(0, 0, 0); - mesh.scale.set(1, 1, 1); - mesh.updateMatrix(); - mesh.applyMatrix4(matrix); - }); - } - newThreeMesh() { - const geometry = new THREE.BufferGeometry(); - const mesh = new THREE.InstancedMesh(geometry, this.model.material, 1); - mesh.frustumCulled = false; - const identity = new THREE.Matrix4().identity(); - mesh.setMatrixAt(0, identity); - mesh.instanceMatrix.needsUpdate = true; - return mesh; - } addSubtraction(geometry) { const item = geometry.ifcData; if (this.clippings.has(item.expressID)) { @@ -77818,28 +77846,21 @@ class ClayGeometry extends ClayObject { this.model.delete(bool); this.update(); } - ifcToThreeGeometry(data) { - const index = this.model.ifcAPI.GetIndexArray(data.GetIndexData(), data.GetIndexDataSize()); - const vertexData = this.model.ifcAPI.GetVertexArray(data.GetVertexData(), data.GetVertexDataSize()); - const position = new Float32Array(vertexData.length / 2); - const normal = new Float32Array(vertexData.length / 2); - for (let i = 0; i < vertexData.length; i += 6) { - position[i / 2] = vertexData[i]; - position[i / 2 + 1] = vertexData[i + 1]; - position[i / 2 + 2] = vertexData[i + 2]; - normal[i / 2] = vertexData[i + 3]; - normal[i / 2 + 1] = vertexData[i + 4]; - normal[i / 2 + 2] = vertexData[i + 5]; - } - const geometry = new THREE.BufferGeometry(); - geometry.setAttribute("position", new THREE.BufferAttribute(position, 3)); - geometry.setAttribute("normal", new THREE.BufferAttribute(normal, 3)); - geometry.setIndex(Array.from(index)); - return geometry; +} + +class MathUtils { + static basisFromEuler(rotation) { + const dirs = new THREE.Matrix4(); + dirs.makeRotationFromEuler(rotation); + const dirX = new THREE.Vector3(); + const dirY = new THREE.Vector3(); + const dirZ = new THREE.Vector3(); + dirs.extractBasis(dirX, dirY, dirZ); + return { dirX, dirY, dirZ }; } } -class IfcGetter { +class IfcUtils { static direction(vector) { return new IFC4X3.IfcDirection([ new IFC4X3.IfcReal(vector.x), @@ -77855,60 +77876,51 @@ class IfcGetter { ]); } static localPlacement(location = new THREE.Vector3(0, 0, 0), zDirection = new THREE.Vector3(0, 0, 1), xDirection = new THREE.Vector3(1, 0, 0)) { - return new IFC4X3.IfcLocalPlacement(null, new IFC4X3.IfcAxis2Placement3D(IfcGetter.point(location), IfcGetter.direction(zDirection), IfcGetter.direction(xDirection))); + return new IFC4X3.IfcLocalPlacement(null, new IFC4X3.IfcAxis2Placement3D(IfcUtils.point(location), IfcUtils.direction(zDirection), IfcUtils.direction(xDirection))); } static shapeRepresentation(model) { return new IFC4X3.IfcShapeRepresentation(model.context, new IFC4X3.IfcLabel("Body"), new IFC4X3.IfcLabel("SweptSolid"), []); } + static setAxis2Placement(model, placement, position, rotation) { + const location = model.get(placement.Location); + location.Coordinates[0].value = position.x; + location.Coordinates[1].value = position.y; + location.Coordinates[2].value = position.z; + model.set(location); + const { dirX, dirZ } = MathUtils.basisFromEuler(rotation); + if (placement instanceof IFC4X3.IfcAxis2Placement3D) { + const zDirection = model.get(placement.Axis); + zDirection.DirectionRatios[0].value = dirZ.x; + zDirection.DirectionRatios[1].value = dirZ.y; + zDirection.DirectionRatios[2].value = dirZ.z; + model.set(zDirection); + } + const xDirection = model.get(placement.RefDirection); + xDirection.DirectionRatios[0].value = dirX.x; + xDirection.DirectionRatios[1].value = dirX.y; + xDirection.DirectionRatios[2].value = dirX.z; + model.set(xDirection); + } } class Extrusion extends ClayGeometry { - get zRotation() { - const { x, z } = this.xDirection; - const result = Math.atan2(x, z); - if (result >= 0) { - return result; - } - return 2 * Math.PI + result; - } - set zRotation(value) { - const matrix = new THREE.Matrix4(); - matrix.makeRotationY(value); - const { elements } = matrix; - this.xDirection.set(elements[8], elements[9], elements[10]); - } constructor(model, profile) { super(model); this.depth = 1; this.position = new THREE.Vector3(0, 0, 0); - this.zDirection = new THREE.Vector3(0, 0, 0); - this.xDirection = new THREE.Vector3(0, 0, 1); + this.rotation = new THREE.Euler(); this.direction = new THREE.Vector3(0, 1, 0); this.profile = profile; - this.mesh = this.newThreeMesh(); - const placement = new IFC4X3.IfcAxis2Placement3D(IfcGetter.point(this.position), IfcGetter.direction(this.zDirection), IfcGetter.direction(this.xDirection)); - const direction = IfcGetter.direction(this.direction); + const { dirX, dirZ } = MathUtils.basisFromEuler(this.rotation); + const placement = new IFC4X3.IfcAxis2Placement3D(IfcUtils.point(this.position), IfcUtils.direction(dirZ), IfcUtils.direction(dirX)); + const direction = IfcUtils.direction(this.direction); this.core = new IFC4X3.IfcExtrudedAreaSolid(profile.ifcData, placement, direction, new IFC4X3.IfcPositiveLengthMeasure(this.depth)); this.ifcData = this.core; this.update(); } update() { const placement = this.model.get(this.core.Position); - const location = this.model.get(placement.Location); - location.Coordinates[0].value = this.position.z; - location.Coordinates[1].value = -this.position.x; - location.Coordinates[2].value = this.position.y; - this.model.set(location); - const zDirection = this.model.get(placement.Axis); - zDirection.DirectionRatios[0].value = this.zDirection.z; - zDirection.DirectionRatios[1].value = this.zDirection.x; - zDirection.DirectionRatios[2].value = this.zDirection.y; - this.model.set(zDirection); - const xDirection = this.model.get(placement.RefDirection); - xDirection.DirectionRatios[0].value = this.xDirection.z; - xDirection.DirectionRatios[1].value = this.xDirection.x; - xDirection.DirectionRatios[2].value = this.xDirection.y; - this.model.set(xDirection); + IfcUtils.setAxis2Placement(this.model, placement, this.position, this.rotation); const direction = this.model.get(this.core.ExtrudedDirection); direction.DirectionRatios[0].value = this.direction.z; direction.DirectionRatios[1].value = this.direction.x; @@ -77916,7 +77928,6 @@ class Extrusion extends ClayGeometry { this.model.set(direction); this.core.Depth.value = this.depth; this.model.set(this.core); - this.setMesh(this.ifcData.expressID, this.mesh); } } @@ -77941,7 +77952,6 @@ class Brep extends ClayGeometry { } constructor(model, geometry) { super(model); - this.mesh = this.newThreeMesh(); this._baseGeometry = geometry; this.core = this.getBrep(); this.ifcData = this.core; @@ -77949,7 +77959,6 @@ class Brep extends ClayGeometry { } update() { this.model.set(this.core); - this.setMesh(this.ifcData.expressID, this.mesh); } getBrep() { const position = this._baseGeometry.getAttribute("position"); @@ -77970,7 +77979,7 @@ class Brep extends ClayGeometry { return new IFC4X3.IfcFacetedBrep(ifcClosedShell); } triangleToIFCFace(triangle) { - const points = triangle.map((vertex) => IfcGetter.point(vertex)); + const points = triangle.map((vertex) => IfcUtils.point(vertex)); const polyLoop = new IFC4X3.IfcPolyLoop(points); const faceBound = new IFC4X3.IfcFaceBound(polyLoop, new IFC4X3.IfcBoolean("T")); return new IFC4X3.IfcFace([faceBound]); @@ -78001,9 +78010,9 @@ class RectangleProfile extends Profile { constructor(model) { super(model); this.dimension = new THREE.Vector3(1, 1, 0); - this.direction = new THREE.Vector3(1, 0, 0); + this.rotation = new THREE.Euler(0, 0, 0); this.position = new THREE.Vector3(0, 0, 0); - const placement = new IFC4X3.IfcAxis2Placement2D(IfcGetter.point(this.position), IfcGetter.direction(this.direction)); + const placement = new IFC4X3.IfcAxis2Placement2D(IfcUtils.point(this.position), IfcUtils.direction(new THREE.Vector3(1, 0, 0))); this.ifcData = new IFC4X3.IfcRectangleProfileDef(IFC4X3.IfcProfileTypeEnum.AREA, null, placement, new IFC4X3.IfcPositiveLengthMeasure(this.dimension.x), new IFC4X3.IfcPositiveLengthMeasure(this.dimension.y)); this.model.set(this.ifcData); } @@ -78011,55 +78020,50 @@ class RectangleProfile extends Profile { this.ifcData.XDim.value = this.dimension.x; this.ifcData.YDim.value = this.dimension.y; const placement = this.model.get(this.ifcData.Position); - const location = this.model.get(placement.Location); - location.Coordinates[0].value = this.position.y; - location.Coordinates[1].value = -this.position.x; - this.model.set(location); - const ifcDirection = this.model.get(placement.RefDirection); - ifcDirection.DirectionRatios[0].value = this.direction.y; - ifcDirection.DirectionRatios[1].value = -this.direction.x; - this.model.set(ifcDirection); + IfcUtils.setAxis2Placement(this.model, placement, this.position, this.rotation); this.model.set(this.ifcData); } } class Family extends ClayObject { -} - -class Opening extends Family { - get mesh() { - return this.geometries.body.mesh; + constructor(model) { + super(model); + this.position = new THREE.Vector3(); + this.rotation = new THREE.Euler(); + this.openings = new Map(); + this.mesh = this.newThreeMesh(); } - get position() { - return this.geometries.body.position; + addOpening(opening) { + const voids = new IFC4X3.IfcRelVoidsElement(new IFC4X3.IfcGloballyUniqueId(v4()), null, null, null, this.ifcData, opening.ifcData); + this.model.set(voids); + const id = opening.ifcData.expressID; + this.openings.set(id, voids); + this.model.update(); } - set position(value) { - this.geometries.body.position = value; + removeOpening(opening) { + const id = opening.ifcData.expressID; + const found = this.openings.get(id); + if (!found) + return; + this.model.delete(found); + this.model.update(); } + updateElement() { + const placement = this.model.get(this.ifcData.ObjectPlacement); + const relPlacement = this.model.get(placement.RelativePlacement); + IfcUtils.setAxis2Placement(this.model, relPlacement, this.position, this.rotation); + this.model.set(this.ifcData); + this.setMesh(this.ifcData.expressID, this.mesh); + } +} + +class Opening extends Family { get baseDimension() { return this.geometries.body.profile.dimension; } set baseDimension(value) { this.geometries.body.profile.dimension = value; } - get xDirection() { - return this.geometries.body.xDirection; - } - set xDirection(value) { - this.geometries.body.xDirection = value; - } - get zDirection() { - return this.geometries.body.zDirection; - } - set zDirection(value) { - this.geometries.body.zDirection = value; - } - get zRotation() { - return this.geometries.body.zRotation; - } - set zRotation(value) { - this.geometries.body.zRotation = value; - } get direction() { return this.geometries.body.direction; } @@ -78077,10 +78081,10 @@ class Opening extends Family { const profile = new RectangleProfile(model); this.geometries = { body: new Extrusion(model, profile) }; const { body } = this.geometries; - body.mesh.material = model.materialT; - const representation = IfcGetter.shapeRepresentation(this.model); + this.mesh.material = model.materialT; + const representation = IfcUtils.shapeRepresentation(this.model); representation.Items = [body.ifcData]; - const placement = IfcGetter.localPlacement(); + const placement = IfcUtils.localPlacement(); const shape = new IFC4X3.IfcProductDefinitionShape(null, null, [ representation, ]); @@ -78092,6 +78096,7 @@ class Opening extends Family { const { body } = this.geometries; body.profile.update(); body.update(); + this.updateElement(); } } @@ -78102,9 +78107,6 @@ class SimpleSlab extends Family { set thickness(value) { this.geometries.body.depth = value; } - get mesh() { - return this.geometries.body.mesh; - } constructor(model) { super(model); const profile = new RectangleProfile(model); @@ -78113,9 +78115,9 @@ class SimpleSlab extends Family { body.depth = 0.3; body.profile.dimension.x = 5; body.profile.dimension.y = 10; - const representation = IfcGetter.shapeRepresentation(this.model); + const representation = IfcUtils.shapeRepresentation(this.model); representation.Items = [body.ifcData]; - const placement = IfcGetter.localPlacement(); + const placement = IfcUtils.localPlacement(); const shape = new IFC4X3.IfcProductDefinitionShape(null, null, [ representation, ]); @@ -78127,19 +78129,16 @@ class SimpleSlab extends Family { const { body } = this.geometries; body.profile.update(); body.update(); + this.updateElement(); } } class SimpleWall extends Family { - get mesh() { - return this.geometries.body.mesh; - } get length() { return this.startPoint.distanceTo(this.endPoint); } get midPoint() { - const vector = new THREE.Vector3((this.startPoint.x + this.endPoint.x) / 2, (this.startPoint.y + this.endPoint.y) / 2, (this.startPoint.z + this.endPoint.z) / 2); - return vector; + return new THREE.Vector3((this.startPoint.x + this.endPoint.x) / 2, (this.startPoint.y + this.endPoint.y) / 2, (this.startPoint.z + this.endPoint.z) / 2); } get direction() { const vector = new THREE.Vector3(); @@ -78157,9 +78156,9 @@ class SimpleWall extends Family { const profile = new RectangleProfile(model); this.geometries = { body: new Extrusion(model, profile) }; const { body } = this.geometries; - const representation = IfcGetter.shapeRepresentation(this.model); + const representation = IfcUtils.shapeRepresentation(this.model); representation.Items = [body.ifcData]; - const placement = IfcGetter.localPlacement(); + const placement = IfcUtils.localPlacement(); const shape = new IFC4X3.IfcProductDefinitionShape(null, null, [ representation, ]); @@ -78172,60 +78171,64 @@ class SimpleWall extends Family { const profile = this.geometries.body.profile; profile.dimension.x = this.length; profile.dimension.y = this.width; - profile.position = this.midPoint; - profile.direction = this.direction; profile.update(); const { body } = this.geometries; body.depth = this.height; body.update(); + const dir = this.direction; + this.rotation.z = Math.atan2(dir.y, dir.x); + this.position = this.midPoint; + this.updateElement(); } addOpening(opening) { + super.addOpening(opening); + this.setOpening(opening); + } + removeOpening(opening) { + super.removeOpening(opening); + this._openings.delete(opening.ifcData.expressID); + } + setOpening(opening) { const wallPlane = new THREE.Plane(); const tempPoint = this.startPoint.clone(); tempPoint.z += 1; wallPlane.setFromCoplanarPoints(tempPoint, this.startPoint, this.endPoint); const newPosition = new THREE.Vector3(); - const position = opening.position; - wallPlane.projectPoint(position, newPosition); - opening.position.z = newPosition.y; - opening.position.x = newPosition.x; + wallPlane.projectPoint(opening.position, newPosition); + opening.position.copy(newPosition); opening.update(); - const distance = newPosition.distanceTo(this.startPoint); + // The distance is signed, so that it also supports openings that are + // before the startPoint by using the dot product + let distance = newPosition.distanceTo(this.startPoint); + const vector = new THREE.Vector3(); + vector.subVectors(newPosition, this.startPoint); + const dotProduct = vector.dot(this.direction); + distance *= dotProduct > 0 ? 1 : -1; const id = opening.ifcData.expressID; this._openings.set(id, { opening, distance }); } - removeOpening(opening) { - this._openings.delete(opening.ifcData.expressID); - } - updateOpening(opening) { - this.removeOpening(opening); - this.addOpening(opening); - } updateAllOpenings() { const start = this.startPoint; const dir = this.direction; for (const [_id, { opening, distance }] of this._openings) { const pos = dir.clone().multiplyScalar(distance).add(start); + // Align opening to wall opening.position.x = pos.x; - opening.position.z = pos.y; - opening.xDirection.x = dir.x; - opening.xDirection.z = -dir.y; + opening.position.y = pos.y; + opening.rotation.z = this.rotation.z; opening.update(); } } } class Furniture extends Family { - get mesh() { - return this.geometries.body.mesh; - } constructor(model, geometry) { super(model); this.geometries = { body: new Brep(model, geometry) }; const { body } = this.geometries; - const representation = IfcGetter.shapeRepresentation(this.model); + const representation = IfcUtils.shapeRepresentation(this.model); representation.Items = [body.ifcData]; - const placement = IfcGetter.localPlacement(); + const placement = IfcUtils.localPlacement(); const shape = new IFC4X3.IfcProductDefinitionShape(null, null, [ representation, ]); @@ -78236,6 +78239,7 @@ class Furniture extends Family { update() { const { body } = this.geometries; body.update(); + this.updateElement(); } } diff --git a/src/base/clay-object.ts b/src/base/clay-object.ts index 0910511..98fd941 100644 --- a/src/base/clay-object.ts +++ b/src/base/clay-object.ts @@ -1,4 +1,5 @@ import * as WEBIFC from "web-ifc"; +import * as THREE from "three"; import { Model } from "./model"; export abstract class ClayObject { @@ -11,4 +12,62 @@ export abstract class ClayObject { protected constructor(model: Model) { this.model = model; } + + setMesh(id: number, mesh: THREE.Mesh) { + const modelID = this.model.modelID; + this.model.ifcAPI.StreamMeshes(modelID, [id], (ifcMesh) => { + mesh.geometry.dispose(); + const { geometryExpressID, flatTransformation } = + ifcMesh.geometries.get(0); + const data = this.model.ifcAPI.GetGeometry(modelID, geometryExpressID); + mesh.geometry = this.ifcToThreeGeometry(data); + const matrix = new THREE.Matrix4().fromArray(flatTransformation); + mesh.position.set(0, 0, 0); + mesh.rotation.set(0, 0, 0); + mesh.scale.set(1, 1, 1); + mesh.updateMatrix(); + mesh.applyMatrix4(matrix); + }); + } + + newThreeMesh() { + const geometry = new THREE.BufferGeometry(); + const mesh = new THREE.InstancedMesh(geometry, this.model.material, 1); + mesh.frustumCulled = false; + const identity = new THREE.Matrix4().identity(); + mesh.setMatrixAt(0, identity); + mesh.instanceMatrix.needsUpdate = true; + return mesh; + } + + private ifcToThreeGeometry(data: WEBIFC.IfcGeometry) { + const index = this.model.ifcAPI.GetIndexArray( + data.GetIndexData(), + data.GetIndexDataSize() + ); + + const vertexData = this.model.ifcAPI.GetVertexArray( + data.GetVertexData(), + data.GetVertexDataSize() + ); + + const position = new Float32Array(vertexData.length / 2); + const normal = new Float32Array(vertexData.length / 2); + + for (let i = 0; i < vertexData.length; i += 6) { + position[i / 2] = vertexData[i]; + position[i / 2 + 1] = vertexData[i + 1]; + position[i / 2 + 2] = vertexData[i + 2]; + + normal[i / 2] = vertexData[i + 3]; + normal[i / 2 + 1] = vertexData[i + 4]; + normal[i / 2 + 2] = vertexData[i + 5]; + } + + const geometry = new THREE.BufferGeometry(); + geometry.setAttribute("position", new THREE.BufferAttribute(position, 3)); + geometry.setAttribute("normal", new THREE.BufferAttribute(normal, 3)); + geometry.setIndex(Array.from(index)); + return geometry; + } } diff --git a/src/base/ifc-getter.ts b/src/base/ifc-getter.ts deleted file mode 100644 index 470a4ce..0000000 --- a/src/base/ifc-getter.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { IFC4X3 as IFC } from "web-ifc"; -import * as THREE from "three"; -import { Model } from "./model"; - -export class IfcGetter { - static direction(vector: THREE.Vector3) { - return new IFC.IfcDirection([ - new IFC.IfcReal(vector.x), - new IFC.IfcReal(vector.y), - new IFC.IfcReal(vector.z), - ]); - } - - static point(vector: THREE.Vector3) { - return new IFC.IfcCartesianPoint([ - new IFC.IfcLengthMeasure(vector.x), - new IFC.IfcLengthMeasure(vector.y), - new IFC.IfcLengthMeasure(vector.z), - ]); - } - - static localPlacement( - location = new THREE.Vector3(0, 0, 0), - zDirection = new THREE.Vector3(0, 0, 1), - xDirection = new THREE.Vector3(1, 0, 0) - ) { - return new IFC.IfcLocalPlacement( - null, - new IFC.IfcAxis2Placement3D( - IfcGetter.point(location), - IfcGetter.direction(zDirection), - IfcGetter.direction(xDirection) - ) - ); - } - - static shapeRepresentation(model: Model) { - return new IFC.IfcShapeRepresentation( - model.context, - new IFC.IfcLabel("Body"), - new IFC.IfcLabel("SweptSolid"), - [] - ); - } -} diff --git a/src/base/model.ts b/src/base/model.ts index 45e6ae9..8dc151b 100644 --- a/src/base/model.ts +++ b/src/base/model.ts @@ -55,4 +55,14 @@ export class Model { } return item; } + + update() { + if (this._modelID === undefined) { + throw new Error("Malformed model!"); + } + const model = this.ifcAPI.SaveModel(this._modelID); + this.ifcAPI.CloseModel(this._modelID); + this._modelID++; + this.ifcAPI.OpenModel(model); + } } diff --git a/src/families/Family/index.ts b/src/families/Family/index.ts index 6a52f92..3bd4735 100644 --- a/src/families/Family/index.ts +++ b/src/families/Family/index.ts @@ -1,8 +1,70 @@ import * as THREE from "three"; -import { ClayObject } from "../../base"; +import { v4 as uuidv4 } from "uuid"; +import { IFC4X3 as IFC } from "web-ifc"; +import { ClayObject, Model } from "../../base"; +import { Opening } from "../Opening"; +import { IfcUtils } from "../../utils/ifc-utils"; export abstract class Family extends ClayObject { + abstract ifcData: IFC.IfcElement; abstract geometries: { [name: string]: ClayObject }; - abstract get mesh(): THREE.InstancedMesh; + mesh: THREE.InstancedMesh; + + position = new THREE.Vector3(); + + rotation = new THREE.Euler(); + + openings = new Map(); + + constructor(model: Model) { + super(model); + this.mesh = this.newThreeMesh(); + } + + addOpening(opening: Opening) { + const voids = new IFC.IfcRelVoidsElement( + new IFC.IfcGloballyUniqueId(uuidv4()), + null, + null, + null, + this.ifcData, + opening.ifcData + ); + + this.model.set(voids); + + const id = opening.ifcData.expressID; + this.openings.set(id, voids); + + this.model.update(); + } + + removeOpening(opening: Opening) { + const id = opening.ifcData.expressID; + const found = this.openings.get(id); + if (!found) return; + this.model.delete(found); + this.model.update(); + } + + updateElement() { + const placement = this.model.get( + this.ifcData.ObjectPlacement + ) as IFC.IfcLocalPlacement; + + const relPlacement = this.model.get( + placement.RelativePlacement + ) as IFC.IfcAxis2Placement3D; + + IfcUtils.setAxis2Placement( + this.model, + relPlacement, + this.position, + this.rotation + ); + + this.model.set(this.ifcData); + this.setMesh(this.ifcData.expressID, this.mesh); + } } diff --git a/src/families/Furniture/SimpleFurniture/index.ts b/src/families/Furniture/SimpleFurniture/index.ts index 9b2a48e..a6b67d2 100644 --- a/src/families/Furniture/SimpleFurniture/index.ts +++ b/src/families/Furniture/SimpleFurniture/index.ts @@ -1,21 +1,16 @@ import { IFC4X3 as IFC } from "web-ifc"; import * as THREE from "three"; import { v4 as uuidv4 } from "uuid"; -import { InstancedMesh } from "three"; import { Model } from "../../../base"; import { Family } from "../../Family"; import { Brep } from "../../../geometries"; -import { IfcGetter } from "../../../base/ifc-getter"; +import { IfcUtils } from "../../../utils/ifc-utils"; export class Furniture extends Family { ifcData: IFC.IfcFurnishingElement; geometries: { body: Brep }; - get mesh(): InstancedMesh { - return this.geometries.body.mesh; - } - constructor(model: Model, geometry: THREE.BufferGeometry) { super(model); @@ -23,9 +18,9 @@ export class Furniture extends Family { const { body } = this.geometries; - const representation = IfcGetter.shapeRepresentation(this.model); + const representation = IfcUtils.shapeRepresentation(this.model); representation.Items = [body.ifcData]; - const placement = IfcGetter.localPlacement(); + const placement = IfcUtils.localPlacement(); const shape = new IFC.IfcProductDefinitionShape(null, null, [ representation, ]); @@ -49,5 +44,6 @@ export class Furniture extends Family { update(): void { const { body } = this.geometries; body.update(); + this.updateElement(); } } diff --git a/src/families/Opening/index.ts b/src/families/Opening/index.ts index 17d18a5..415d6b1 100644 --- a/src/families/Opening/index.ts +++ b/src/families/Opening/index.ts @@ -4,25 +4,13 @@ import { v4 as uuidv4 } from "uuid"; import { Model } from "../../base"; import { Extrusion, RectangleProfile } from "../../geometries"; import { Family } from "../Family"; -import { IfcGetter } from "../../base/ifc-getter"; +import { IfcUtils } from "../../utils/ifc-utils"; export class Opening extends Family { ifcData: IFC.IfcOpeningElement; geometries: { body: Extrusion }; - get mesh() { - return this.geometries.body.mesh; - } - - get position() { - return this.geometries.body.position; - } - - set position(value: THREE.Vector3) { - this.geometries.body.position = value; - } - get baseDimension() { return this.geometries.body.profile.dimension; } @@ -31,30 +19,6 @@ export class Opening extends Family { this.geometries.body.profile.dimension = value; } - get xDirection() { - return this.geometries.body.xDirection; - } - - set xDirection(value: THREE.Vector3) { - this.geometries.body.xDirection = value; - } - - get zDirection() { - return this.geometries.body.zDirection; - } - - set zDirection(value: THREE.Vector3) { - this.geometries.body.zDirection = value; - } - - get zRotation() { - return this.geometries.body.zRotation; - } - - set zRotation(value: number) { - this.geometries.body.zRotation = value; - } - get direction() { return this.geometries.body.direction; } @@ -78,11 +42,11 @@ export class Opening extends Family { this.geometries = { body: new Extrusion(model, profile) }; const { body } = this.geometries; - body.mesh.material = model.materialT; + this.mesh.material = model.materialT; - const representation = IfcGetter.shapeRepresentation(this.model); + const representation = IfcUtils.shapeRepresentation(this.model); representation.Items = [body.ifcData]; - const placement = IfcGetter.localPlacement(); + const placement = IfcUtils.localPlacement(); const shape = new IFC.IfcProductDefinitionShape(null, null, [ representation, ]); @@ -108,5 +72,6 @@ export class Opening extends Family { const { body } = this.geometries; body.profile.update(); body.update(); + this.updateElement(); } } diff --git a/src/families/Slabs/SimpleSlab/index.ts b/src/families/Slabs/SimpleSlab/index.ts index 37935ae..9648518 100644 --- a/src/families/Slabs/SimpleSlab/index.ts +++ b/src/families/Slabs/SimpleSlab/index.ts @@ -3,7 +3,7 @@ import { IFC4X3 as IFC } from "web-ifc"; import { Model } from "../../../base"; import { Family } from "../../Family"; import { Extrusion, RectangleProfile } from "../../../geometries"; -import { IfcGetter } from "../../../base/ifc-getter"; +import { IfcUtils } from "../../../utils/ifc-utils"; export class SimpleSlab extends Family { ifcData: IFC.IfcSlab; @@ -18,10 +18,6 @@ export class SimpleSlab extends Family { this.geometries.body.depth = value; } - get mesh() { - return this.geometries.body.mesh; - } - constructor(model: Model) { super(model); @@ -33,9 +29,9 @@ export class SimpleSlab extends Family { body.profile.dimension.x = 5; body.profile.dimension.y = 10; - const representation = IfcGetter.shapeRepresentation(this.model); + const representation = IfcUtils.shapeRepresentation(this.model); representation.Items = [body.ifcData]; - const placement = IfcGetter.localPlacement(); + const placement = IfcUtils.localPlacement(); const shape = new IFC.IfcProductDefinitionShape(null, null, [ representation, ]); @@ -61,5 +57,6 @@ export class SimpleSlab extends Family { const { body } = this.geometries; body.profile.update(); body.update(); + this.updateElement(); } } diff --git a/src/families/Walls/SimpleWall/index.html b/src/families/Walls/SimpleWall/index.html index 60e4055..4ab1e5f 100644 --- a/src/families/Walls/SimpleWall/index.html +++ b/src/families/Walls/SimpleWall/index.html @@ -98,15 +98,21 @@ const opening = new CLAY.Opening(model); scene.add(opening.mesh); - const opening2 = new CLAY.Opening(model); - opening2.position.x += 2; - opening2.update(); - scene.add(opening2.mesh); + // const opening2 = new CLAY.Opening(model); + // opening2.position.x += 2; + // opening2.update(); + // scene.add(opening2.mesh); - wall.geometries.body.addSubtraction(opening.geometries.body); - wall.geometries.body.addSubtraction(opening2.geometries.body); + // wall.geometries.body.addSubtraction(opening.geometries.body); + // wall.geometries.body.addSubtraction(opening2.geometries.body); wall.addOpening(opening); + wall.update(); + + window.addEventListener("keydown", () => { + wall.removeOpening(opening); + wall.update(); + }) // Set up GUI @@ -136,14 +142,13 @@ wall.update(); }); - gui.add(opening.position, "x").name("Opening 1 X Position").min(0).max(Math.PI * 2).step( Math.PI / 180).onChange(() => { - opening.update(); - wall.updateOpening(opening); + gui.add(opening.position, "x").name("Opening X Position").min(-5).max(5).step(0.1).onChange(() => { + wall.setOpening(opening); wall.update(); }); - gui.add(opening2.position, "x").name("Opening 2 X Position").min(0).max(Math.PI * 2).step( Math.PI / 180).onChange(() => { - opening2.update(); + gui.add(opening.position, "y").name("Opening Y Position").min(-5).max(5).step(0.1).onChange(() => { + wall.setOpening(opening); wall.update(); }); diff --git a/src/families/Walls/SimpleWall/index.ts b/src/families/Walls/SimpleWall/index.ts index d4489e9..a0c2f78 100644 --- a/src/families/Walls/SimpleWall/index.ts +++ b/src/families/Walls/SimpleWall/index.ts @@ -4,8 +4,8 @@ import { v4 as uuidv4 } from "uuid"; import { Model } from "../../../base"; import { Extrusion, RectangleProfile } from "../../../geometries"; import { Family } from "../../Family"; -import { IfcGetter } from "../../../base/ifc-getter"; import { Opening } from "../../Opening"; +import { IfcUtils } from "../../../utils/ifc-utils"; export class SimpleWall extends Family { ifcData: IFC.IfcWall; @@ -22,21 +22,16 @@ export class SimpleWall extends Family { private _openings = new Map(); - get mesh() { - return this.geometries.body.mesh; - } - get length() { return this.startPoint.distanceTo(this.endPoint); } get midPoint() { - const vector = new THREE.Vector3( + return new THREE.Vector3( (this.startPoint.x + this.endPoint.x) / 2, (this.startPoint.y + this.endPoint.y) / 2, (this.startPoint.z + this.endPoint.z) / 2 ); - return vector; } get direction() { @@ -54,9 +49,9 @@ export class SimpleWall extends Family { const { body } = this.geometries; - const representation = IfcGetter.shapeRepresentation(this.model); + const representation = IfcUtils.shapeRepresentation(this.model); representation.Items = [body.ifcData]; - const placement = IfcGetter.localPlacement(); + const placement = IfcUtils.localPlacement(); const shape = new IFC.IfcProductDefinitionShape(null, null, [ representation, ]); @@ -84,53 +79,64 @@ export class SimpleWall extends Family { const profile = this.geometries.body.profile; profile.dimension.x = this.length; profile.dimension.y = this.width; - profile.position = this.midPoint; - profile.direction = this.direction; profile.update(); const { body } = this.geometries; body.depth = this.height; body.update(); + + const dir = this.direction; + this.rotation.z = Math.atan2(dir.y, dir.x); + this.position = this.midPoint; + + this.updateElement(); } addOpening(opening: Opening) { + super.addOpening(opening); + this.setOpening(opening); + } + + removeOpening(opening: Opening) { + super.removeOpening(opening); + this._openings.delete(opening.ifcData.expressID); + } + + setOpening(opening: Opening) { const wallPlane = new THREE.Plane(); + const tempPoint = this.startPoint.clone(); tempPoint.z += 1; wallPlane.setFromCoplanarPoints(tempPoint, this.startPoint, this.endPoint); - const newPosition = new THREE.Vector3(); - const position = opening.position; - wallPlane.projectPoint(position, newPosition); - opening.position.z = newPosition.y; - opening.position.x = newPosition.x; + wallPlane.projectPoint(opening.position, newPosition); + + opening.position.copy(newPosition); opening.update(); - const distance = newPosition.distanceTo(this.startPoint); + // The distance is signed, so that it also supports openings that are + // before the startPoint by using the dot product + let distance = newPosition.distanceTo(this.startPoint); + const vector = new THREE.Vector3(); + vector.subVectors(newPosition, this.startPoint); + const dotProduct = vector.dot(this.direction); + distance *= dotProduct > 0 ? 1 : -1; + const id = opening.ifcData.expressID; this._openings.set(id, { opening, distance }); } - removeOpening(opening: Opening) { - this._openings.delete(opening.ifcData.expressID); - } - - updateOpening(opening: Opening) { - this.removeOpening(opening); - this.addOpening(opening); - } - private updateAllOpenings() { const start = this.startPoint; const dir = this.direction; for (const [_id, { opening, distance }] of this._openings) { const pos = dir.clone().multiplyScalar(distance).add(start); - opening.position.x = pos.x; - opening.position.z = pos.y; - opening.xDirection.x = dir.x; - opening.xDirection.z = -dir.y; + // Align opening to wall + opening.position.x = pos.x; + opening.position.y = pos.y; + opening.rotation.z = this.rotation.z; opening.update(); } diff --git a/src/geometries/Brep/index.ts b/src/geometries/Brep/index.ts index 349bfe3..ecef997 100644 --- a/src/geometries/Brep/index.ts +++ b/src/geometries/Brep/index.ts @@ -2,15 +2,13 @@ import { IFC4X3 as IFC } from "web-ifc"; import * as THREE from "three"; import { ClayGeometry } from "../Geometry"; import { Model } from "../../base"; -import { IfcGetter } from "../../base/ifc-getter"; +import { IfcUtils } from "../../utils/ifc-utils"; export class Brep extends ClayGeometry { ifcData: IFC.IfcFacetedBrep | IFC.IfcBooleanClippingResult; core: IFC.IfcFacetedBrep; - mesh: THREE.InstancedMesh; - private _baseGeometry: THREE.BufferGeometry; get baseGeometry() { @@ -36,7 +34,6 @@ export class Brep extends ClayGeometry { constructor(model: Model, geometry: THREE.BufferGeometry) { super(model); - this.mesh = this.newThreeMesh(); this._baseGeometry = geometry; this.core = this.getBrep(); this.ifcData = this.core; @@ -45,7 +42,6 @@ export class Brep extends ClayGeometry { update() { this.model.set(this.core); - this.setMesh(this.ifcData.expressID, this.mesh); } private getBrep() { @@ -83,7 +79,7 @@ export class Brep extends ClayGeometry { } private triangleToIFCFace(triangle: THREE.Vector3[]) { - const points = triangle.map((vertex) => IfcGetter.point(vertex)); + const points = triangle.map((vertex) => IfcUtils.point(vertex)); const polyLoop = new IFC.IfcPolyLoop(points); const faceBound = new IFC.IfcFaceBound(polyLoop, new IFC.IfcBoolean("T")); return new IFC.IfcFace([faceBound]); diff --git a/src/geometries/Extrusion/index.ts b/src/geometries/Extrusion/index.ts index a7b954b..65ac61c 100644 --- a/src/geometries/Extrusion/index.ts +++ b/src/geometries/Extrusion/index.ts @@ -3,56 +3,37 @@ import * as THREE from "three"; import { Model } from "../../base"; import { Profile } from "../Profiles/Profile"; import { ClayGeometry } from "../Geometry"; -import { IfcGetter } from "../../base/ifc-getter"; +import { MathUtils } from "../../utils/math-utils"; +import { IfcUtils } from "../../utils/ifc-utils"; export class Extrusion extends ClayGeometry { ifcData: IFC.IfcExtrudedAreaSolid | IFC.IfcBooleanClippingResult; core: IFC.IfcExtrudedAreaSolid; - mesh: THREE.InstancedMesh; - depth = 1; profile: T; position = new THREE.Vector3(0, 0, 0); - zDirection = new THREE.Vector3(0, 0, 0); - - xDirection = new THREE.Vector3(0, 0, 1); + rotation = new THREE.Euler(); direction = new THREE.Vector3(0, 1, 0); - get zRotation() { - const { x, z } = this.xDirection; - const result = Math.atan2(x, z); - if (result >= 0) { - return result; - } - return 2 * Math.PI + result; - } - - set zRotation(value: number) { - const matrix = new THREE.Matrix4(); - matrix.makeRotationY(value); - const { elements } = matrix; - this.xDirection.set(elements[8], elements[9], elements[10]); - } - constructor(model: Model, profile: T) { super(model); this.profile = profile; - this.mesh = this.newThreeMesh(); + const { dirX, dirZ } = MathUtils.basisFromEuler(this.rotation); const placement = new IFC.IfcAxis2Placement3D( - IfcGetter.point(this.position), - IfcGetter.direction(this.zDirection), - IfcGetter.direction(this.xDirection) + IfcUtils.point(this.position), + IfcUtils.direction(dirZ), + IfcUtils.direction(dirX) ); - const direction = IfcGetter.direction(this.direction); + const direction = IfcUtils.direction(this.direction); this.core = new IFC.IfcExtrudedAreaSolid( profile.ifcData, @@ -69,26 +50,12 @@ export class Extrusion extends ClayGeometry { update() { const placement = this.model.get(this.core.Position); - const location = this.model.get( - placement.Location - ) as IFC.IfcCartesianPoint; - - location.Coordinates[0].value = this.position.z; - location.Coordinates[1].value = -this.position.x; - location.Coordinates[2].value = this.position.y; - this.model.set(location); - - const zDirection = this.model.get(placement.Axis); - zDirection.DirectionRatios[0].value = this.zDirection.z; - zDirection.DirectionRatios[1].value = this.zDirection.x; - zDirection.DirectionRatios[2].value = this.zDirection.y; - this.model.set(zDirection); - - const xDirection = this.model.get(placement.RefDirection); - xDirection.DirectionRatios[0].value = this.xDirection.z; - xDirection.DirectionRatios[1].value = this.xDirection.x; - xDirection.DirectionRatios[2].value = this.xDirection.y; - this.model.set(xDirection); + IfcUtils.setAxis2Placement( + this.model, + placement, + this.position, + this.rotation + ); const direction = this.model.get(this.core.ExtrudedDirection); direction.DirectionRatios[0].value = this.direction.z; @@ -99,6 +66,5 @@ export class Extrusion extends ClayGeometry { this.core.Depth.value = this.depth; this.model.set(this.core); - this.setMesh(this.ifcData.expressID, this.mesh); } } diff --git a/src/geometries/Geometry/index.ts b/src/geometries/Geometry/index.ts index 82cf27a..25fbe17 100644 --- a/src/geometries/Geometry/index.ts +++ b/src/geometries/Geometry/index.ts @@ -1,6 +1,4 @@ import { IFC4X3 as IFC, IfcLineObject } from "web-ifc"; -import * as THREE from "three"; -import * as WEBIFC from "web-ifc"; import { ClayObject } from "../../base"; export abstract class ClayGeometry extends ClayObject { @@ -22,33 +20,6 @@ export abstract class ClayGeometry extends ClayObject { } >(); - setMesh(id: number, mesh: THREE.Mesh) { - const modelID = this.model.modelID; - this.model.ifcAPI.StreamMeshes(modelID, [id], (ifcMesh) => { - mesh.geometry.dispose(); - const { geometryExpressID, flatTransformation } = - ifcMesh.geometries.get(0); - const data = this.model.ifcAPI.GetGeometry(modelID, geometryExpressID); - mesh.geometry = this.ifcToThreeGeometry(data); - const matrix = new THREE.Matrix4().fromArray(flatTransformation); - mesh.position.set(0, 0, 0); - mesh.rotation.set(0, 0, 0); - mesh.scale.set(1, 1, 1); - mesh.updateMatrix(); - mesh.applyMatrix4(matrix); - }); - } - - newThreeMesh() { - const geometry = new THREE.BufferGeometry(); - const mesh = new THREE.InstancedMesh(geometry, this.model.material, 1); - mesh.frustumCulled = false; - const identity = new THREE.Matrix4().identity(); - mesh.setMatrixAt(0, identity); - mesh.instanceMatrix.needsUpdate = true; - return mesh; - } - addSubtraction(geometry: ClayGeometry) { const item = geometry.ifcData; @@ -141,35 +112,4 @@ export abstract class ClayGeometry extends ClayObject { this.model.delete(bool); this.update(); } - - private ifcToThreeGeometry(data: WEBIFC.IfcGeometry) { - const index = this.model.ifcAPI.GetIndexArray( - data.GetIndexData(), - data.GetIndexDataSize() - ); - - const vertexData = this.model.ifcAPI.GetVertexArray( - data.GetVertexData(), - data.GetVertexDataSize() - ); - - const position = new Float32Array(vertexData.length / 2); - const normal = new Float32Array(vertexData.length / 2); - - for (let i = 0; i < vertexData.length; i += 6) { - position[i / 2] = vertexData[i]; - position[i / 2 + 1] = vertexData[i + 1]; - position[i / 2 + 2] = vertexData[i + 2]; - - normal[i / 2] = vertexData[i + 3]; - normal[i / 2 + 1] = vertexData[i + 4]; - normal[i / 2 + 2] = vertexData[i + 5]; - } - - const geometry = new THREE.BufferGeometry(); - geometry.setAttribute("position", new THREE.BufferAttribute(position, 3)); - geometry.setAttribute("normal", new THREE.BufferAttribute(normal, 3)); - geometry.setIndex(Array.from(index)); - return geometry; - } } diff --git a/src/geometries/Profiles/RectangleProfile/index.ts b/src/geometries/Profiles/RectangleProfile/index.ts index 9b71da9..5b14b83 100644 --- a/src/geometries/Profiles/RectangleProfile/index.ts +++ b/src/geometries/Profiles/RectangleProfile/index.ts @@ -2,14 +2,14 @@ import * as THREE from "three"; import { IFC4X3 as IFC } from "web-ifc"; import { Profile } from "../Profile"; import { Model } from "../../../base"; -import { IfcGetter } from "../../../base/ifc-getter"; +import { IfcUtils } from "../../../utils/ifc-utils"; export class RectangleProfile extends Profile { ifcData: IFC.IfcRectangleProfileDef; dimension = new THREE.Vector3(1, 1, 0); - direction = new THREE.Vector3(1, 0, 0); + rotation = new THREE.Euler(0, 0, 0); position = new THREE.Vector3(0, 0, 0); @@ -17,8 +17,8 @@ export class RectangleProfile extends Profile { super(model); const placement = new IFC.IfcAxis2Placement2D( - IfcGetter.point(this.position), - IfcGetter.direction(this.direction) + IfcUtils.point(this.position), + IfcUtils.direction(new THREE.Vector3(1, 0, 0)) ); this.ifcData = new IFC.IfcRectangleProfileDef( @@ -38,18 +38,12 @@ export class RectangleProfile extends Profile { const placement = this.model.get(this.ifcData.Position); - const location = this.model.get( - placement.Location - ) as IFC.IfcCartesianPoint; - - location.Coordinates[0].value = this.position.y; - location.Coordinates[1].value = -this.position.x; - this.model.set(location); - - const ifcDirection = this.model.get(placement.RefDirection); - ifcDirection.DirectionRatios[0].value = this.direction.y; - ifcDirection.DirectionRatios[1].value = -this.direction.x; - this.model.set(ifcDirection); + IfcUtils.setAxis2Placement( + this.model, + placement, + this.position, + this.rotation + ); this.model.set(this.ifcData); } diff --git a/src/utils/ifc-utils.ts b/src/utils/ifc-utils.ts new file mode 100644 index 0000000..1242193 --- /dev/null +++ b/src/utils/ifc-utils.ts @@ -0,0 +1,76 @@ +import { IFC4X3 as IFC } from "web-ifc"; +import * as THREE from "three"; +import { Model } from "../base"; +import { MathUtils } from "./math-utils"; + +export class IfcUtils { + static direction(vector: THREE.Vector3) { + return new IFC.IfcDirection([ + new IFC.IfcReal(vector.x), + new IFC.IfcReal(vector.y), + new IFC.IfcReal(vector.z), + ]); + } + + static point(vector: THREE.Vector3) { + return new IFC.IfcCartesianPoint([ + new IFC.IfcLengthMeasure(vector.x), + new IFC.IfcLengthMeasure(vector.y), + new IFC.IfcLengthMeasure(vector.z), + ]); + } + + static localPlacement( + location = new THREE.Vector3(0, 0, 0), + zDirection = new THREE.Vector3(0, 0, 1), + xDirection = new THREE.Vector3(1, 0, 0) + ) { + return new IFC.IfcLocalPlacement( + null, + new IFC.IfcAxis2Placement3D( + IfcUtils.point(location), + IfcUtils.direction(zDirection), + IfcUtils.direction(xDirection) + ) + ); + } + + static shapeRepresentation(model: Model) { + return new IFC.IfcShapeRepresentation( + model.context, + new IFC.IfcLabel("Body"), + new IFC.IfcLabel("SweptSolid"), + [] + ); + } + + static setAxis2Placement( + model: Model, + placement: IFC.IfcAxis2Placement3D | IFC.IfcAxis2Placement2D, + position: THREE.Vector3, + rotation: THREE.Euler + ) { + const location = model.get(placement.Location) as IFC.IfcCartesianPoint; + + location.Coordinates[0].value = position.x; + location.Coordinates[1].value = position.y; + location.Coordinates[2].value = position.z; + model.set(location); + + const { dirX, dirZ } = MathUtils.basisFromEuler(rotation); + + if (placement instanceof IFC.IfcAxis2Placement3D) { + const zDirection = model.get(placement.Axis); + zDirection.DirectionRatios[0].value = dirZ.x; + zDirection.DirectionRatios[1].value = dirZ.y; + zDirection.DirectionRatios[2].value = dirZ.z; + model.set(zDirection); + } + + const xDirection = model.get(placement.RefDirection); + xDirection.DirectionRatios[0].value = dirX.x; + xDirection.DirectionRatios[1].value = dirX.y; + xDirection.DirectionRatios[2].value = dirX.z; + model.set(xDirection); + } +} diff --git a/src/utils/math-utils.ts b/src/utils/math-utils.ts new file mode 100644 index 0000000..a9f8dcb --- /dev/null +++ b/src/utils/math-utils.ts @@ -0,0 +1,13 @@ +import * as THREE from "three"; + +export class MathUtils { + static basisFromEuler(rotation: THREE.Euler) { + const dirs = new THREE.Matrix4(); + dirs.makeRotationFromEuler(rotation); + const dirX = new THREE.Vector3(); + const dirY = new THREE.Vector3(); + const dirZ = new THREE.Vector3(); + dirs.extractBasis(dirX, dirY, dirZ); + return { dirX, dirY, dirZ }; + } +}