123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619 |
- /**
- * ObjectUtils.js
- *
- * @author realor
- */
- import { Cord } from '../core/Cord.js'
- import { Profile } from '../core/Profile.js'
- import { Solid } from '../core/Solid.js'
- import { SolidGeometry } from '../core/SolidGeometry.js'
- import * as THREE from '../lib/three.module.js'
- class ObjectUtils {
- static ORIGINAL_MATERIAL = 'originalMaterial'
- static METER_CONVERSION_FACTORS = {
- km: 0.001,
- m: 1,
- cm: 100,
- mm: 1000,
- in: 39.3701
- }
- static getObjectValue(object, ...properties) {
- if (properties.length === 0) return object
- let data = object
- let i = 0
- while (i < properties.length && data && typeof data === 'object') {
- let property = properties[i]
- if (data instanceof THREE.Object3D) {
- if (typeof property === 'string') {
- if (property.startsWith('child:')) {
- let childName = property.substring(6)
- let children = data.children
- data = null
- for (let child of children) {
- if (child.name === childName) {
- data = child
- break
- }
- }
- } else if (property.startsWith('descendant:')) {
- let descendantName = property.substring(11)
- let children = data.children
- for (let child of children) {
- data = child.getObjectByName(descendantName)
- if (data) break
- }
- } else if (property.startsWith('ancestor:')) {
- let ancestorName = property.substring(9)
- let ancestor = data.parent
- while (ancestor !== null && ancestor.name !== ancestorName) {
- ancestor = ancestor.parent
- }
- data = ancestor
- } else {
- let value = data[property]
- data = value === undefined ? data.userData[property] : value
- }
- } else if (typeof property === 'number') {
- data = data.children[property]
- } else {
- data = undefined
- }
- } else {
- data = data[property]
- }
- i++
- }
- if (data === null || data === undefined) data = ''
- return data
- }
- /**
- * Creates a function to evaluate a expression for an object
- *
- * @param {String|Function} expression : an expression with references to
- * $ function. When expression is a String it can also have references to
- * these object properties: object, position, rotation, scale, material,
- * userData, builder and controllers.
- *
- * @returns {Function} : function fn(object) that evaluates expression for
- * the given object.
- *
- * Examples:
- * ObjectUtils.createEvalFunction('$("IFC", "ifcClassName") === "IfcBeam"')
- * ObjectUtils.createEvalFunction($ => $("IFC", "Name") === "House")
- *
- */
- static createEvalFunction(expression) {
- if (typeof expression === 'string') {
- let fn = new Function('object', 'position', 'rotation', 'scale', 'material', 'userData', 'builder', 'controllers', '$', 'return ' + expression + ';')
- return object => {
- const $ = (...properties) => this.getObjectValue(object, ...properties)
- return fn(object, object.position, object.rotation, object.scale, object.material, object.userData, object.builder, object.controllers, $)
- }
- } else if (typeof expression === 'function') {
- return object => {
- const $ = (...properties) => this.getObjectValue(object, ...properties)
- return expression($)
- }
- } else {
- return () => expression
- }
- }
- static dispose(root, geometries = true, materials = true) {
- root.traverse(object => {
- if (geometries && object.geometry) {
- const geometry = object.geometry
- if (geometry.dispose) {
- geometry.dispose()
- }
- }
- if (materials && object.material) {
- this.disposeMaterial(object.material)
- }
- })
- }
- static disposeMaterial(material) {
- if (material instanceof THREE.Material) {
- material.dispose()
- } else if (material instanceof Array) {
- for (let mat of material) {
- mat.dispose()
- }
- }
- }
- static applyMaterial(object, material, original = true) {
- const ORIGINAL_MATERIAL = ObjectUtils.ORIGINAL_MATERIAL
- let changed = false
- if (material === null) {
- // restore original material if possible
- if (object[ORIGINAL_MATERIAL]) {
- this.disposeMaterial(object.material)
- object.material = object[ORIGINAL_MATERIAL]
- object[ORIGINAL_MATERIAL] = null
- changed = true
- }
- } else if (material instanceof THREE.Material) {
- if (original) {
- if (object[ORIGINAL_MATERIAL]) {
- this.disposeMaterial(object[ORIGINAL_MATERIAL])
- object[ORIGINAL_MATERIAL] = null
- }
- } else {
- if (object[ORIGINAL_MATERIAL]) {
- // original material already saved
- } else {
- object[ORIGINAL_MATERIAL] = object.material
- }
- }
- if (object.material !== material) {
- this.disposeMaterial(object.material)
- object.material = material
- changed = true
- }
- }
- return changed
- }
- static findObjects(condition, baseObject, nested = false) {
- const objects = []
- function traverse(object) {
- let accepted = condition(object)
- if (accepted) {
- objects.push(object)
- }
- if (!accepted || nested) {
- if (!(object instanceof Solid)) {
- const children = object.children
- for (let child of children) {
- traverse(child)
- }
- }
- }
- }
- traverse(baseObject)
- return objects
- }
- static updateVisibility(objects, visible) {
- return this.updateAppearance(objects, { visible: visible })
- }
- static updateStyle(objects, edgesVisible = true, facesVisible = true) {
- return this.updateAppearance(objects, { edgesVisible: edgesVisible, facesVisible: facesVisible })
- }
- static updateAppearance(objects, appearance) {
- const visited = appearance.visible ? new Set() : null
- const setupMaterial = (property, materialClass) => {
- let material = appearance[property]
- if (material instanceof THREE.Material || material === null) return material
- if (typeof material === 'object') {
- return new materialClass(material)
- }
- return undefined
- }
- let meshMaterial = setupMaterial('meshMaterial', THREE.MeshPhongMaterial)
- let lineMaterial = setupMaterial('lineMaterial', THREE.LineBasicMaterial)
- let pointsMaterial = setupMaterial('pointsMaterial', THREE.PointsMaterial)
- return ObjectUtils.updateObjects(
- objects,
- (object, changed) => {
- if (appearance.visible !== undefined) {
- if (object.visible !== appearance.visible) {
- object.visible = appearance.visible
- changed.add(object)
- if (object.visible) {
- // make ancestors visible
- let ancestor = object.parent
- while (ancestor !== null && !visited.has(ancestor)) {
- if (ancestor.visible === false) {
- ancestor.visible = true
- changed.add(ancestor)
- }
- visited.add(ancestor)
- ancestor = ancestor.parent
- }
- }
- }
- }
- const original = appearance.original === true
- if (object instanceof THREE.Mesh) {
- if (ObjectUtils.applyMaterial(object, meshMaterial, original)) {
- changed.add(object)
- }
- } else if (object instanceof THREE.Line) {
- if (ObjectUtils.applyMaterial(object, lineMaterial, original)) {
- changed.add(object)
- }
- } else if (object instanceof THREE.Points) {
- if (ObjectUtils.applyMaterial(object, pointsMaterial, original)) {
- changed.add(object)
- }
- } else if (object instanceof Solid) {
- if (appearance.facesVisible !== undefined) {
- if (object.facesVisible !== appearance.facesVisible) {
- object.facesVisible = appearance.facesVisible
- changed.add(object)
- }
- }
- if (appearance.edgesVisible !== undefined) {
- if (object.edgesVisible !== appearance.edgesVisible) {
- object.edgesVisible = appearance.edgesVisible
- changed.add(object)
- }
- }
- if (ObjectUtils.applyMaterial(object.facesObject, meshMaterial, original)) {
- changed.add(object)
- }
- if (ObjectUtils.applyMaterial(object.edgesObject, lineMaterial, original)) {
- changed.add(object)
- }
- } else if (object instanceof Profile) {
- if (ObjectUtils.applyMaterial(object, lineMaterial, original)) {
- changed.add(object)
- }
- } else if (object instanceof Cord) {
- if (ObjectUtils.applyMaterial(object, lineMaterial, original)) {
- changed.add(object)
- }
- }
- },
- true
- )
- }
- static updateObjects(objects, updateFunction, recursive = false) {
- const changed = new Set()
- if (objects instanceof THREE.Object3D) {
- objects = [objects]
- }
- function traverse(object) {
- updateFunction(object, changed)
- if (
- !(object instanceof Solid) &&
- !(object instanceof Profile) &&
- !(object instanceof Cord) &&
- !(object instanceof THREE.Mesh) &&
- !(object instanceof THREE.Line) &&
- !(object instanceof THREE.Points)
- ) {
- const children = object.children
- for (let child of children) {
- traverse(child)
- }
- }
- }
- if (recursive) {
- for (let object of objects) {
- traverse(object)
- }
- } else {
- for (let object of objects) {
- updateFunction(object, changed)
- }
- }
- return changed
- }
- static zoomAll(camera, objects, aspect, all) {
- if (objects instanceof THREE.Object3D) {
- objects = [objects]
- }
- let box = ObjectUtils.getBoundingBoxFromView(objects, camera.matrixWorld, all) // box in camera CS
- if (box.isEmpty()) return
- let center = new THREE.Vector3()
- center = box.getCenter(center) // center in camera CS
- let matrix = camera.matrix
- center.applyMatrix4(camera.matrix) // center in camera parent CS
- let boxWidth = box.max.x - box.min.x
- let boxHeight = box.max.y - box.min.y
- let boxDepth = box.max.z - box.min.z
- let offset
- if (camera instanceof THREE.PerspectiveCamera) {
- let ymax = camera.near * Math.tan(THREE.MathUtils.degToRad(camera.fov * 0.5))
- let xmax = ymax * camera.aspect
- let yoffset = (boxHeight * camera.near) / (2 * ymax)
- let xoffset = (boxWidth * camera.near) / (2 * xmax)
- offset = Math.max(xoffset, yoffset) + 0.5 * boxDepth
- } // Ortho camera
- else {
- let factor = 0.5 * 1.1 // 10% extra space
- camera.left = -factor * boxWidth
- camera.right = factor * boxWidth
- camera.top = factor * boxHeight
- camera.bottom = -factor * boxHeight
- offset = camera.far - boxDepth
- }
- let v = new THREE.Vector3()
- v.setFromMatrixColumn(matrix, 2) // view vector (zaxis) in parent CS
- v.normalize()
- v.multiplyScalar(offset)
- center.add(v)
- camera.zoom = 1
- camera.position.copy(center)
- camera.updateMatrix()
- this.updateCameraAspectRatio(camera, aspect)
- }
- static updateCameraAspectRatio(camera, aspect) {
- if (camera instanceof THREE.PerspectiveCamera) {
- camera.aspect = aspect
- camera.updateProjectionMatrix()
- } else if (camera instanceof THREE.OrthographicCamera) {
- var width = camera.right - camera.left
- var height = camera.top - camera.bottom
- var currentAspect = width / height
- if (aspect < currentAspect) {
- var h = 0.5 * (width / aspect - height)
- camera.top += h
- camera.bottom -= h
- } else {
- var w = 0.5 * (height * aspect - width)
- camera.left -= w
- camera.right += w
- }
- camera.updateProjectionMatrix()
- }
- }
- static getBoundingBoxFromView(objects, viewMatrixWorld, all = false) {
- const box = new THREE.Box3() // empty box
- const vertex = new THREE.Vector3()
- const inverseMatrix = new THREE.Matrix4()
- inverseMatrix.copy(viewMatrixWorld).invert()
- function extendBox(object) {
- let geometry = object.geometry
- if (geometry) {
- if (geometry instanceof SolidGeometry) {
- let vertices = geometry.vertices
- for (let j = 0; j < vertices.length; j++) {
- vertex.copy(vertices[j])
- vertex.applyMatrix4(object.matrixWorld) // world CS
- vertex.applyMatrix4(inverseMatrix) // view CS
- box.expandByPoint(vertex)
- }
- } else if (geometry instanceof THREE.BufferGeometry) {
- let position = geometry.attributes.position
- if (position) {
- const positions = position.array
- for (let j = 0; j < positions.length; j += 3) {
- vertex.set(positions[j], positions[j + 1], positions[j + 2])
- vertex.applyMatrix4(object.matrixWorld) // world CS
- vertex.applyMatrix4(inverseMatrix) // view CS
- box.expandByPoint(vertex)
- }
- }
- }
- }
- }
- function traverse(object) {
- if (object.visible || all) {
- extendBox(object)
- if (!(object instanceof Solid || object instanceof Cord || object instanceof Profile)) {
- for (let child of object.children) {
- traverse(child)
- }
- }
- }
- }
- for (let object of objects) {
- traverse(object)
- }
- return box
- }
- static getLocalBoundingBox(object, all = false) {
- const box = new THREE.Box3() // empty box
- const objectBox = new THREE.Box3()
- function extendBox(object, toBaseMatrix) {
- if (object.visible || all) {
- let geometry = object.geometry
- if (geometry) {
- if (geometry.boundingBox === null) {
- geometry.computeBoundingBox()
- }
- if (!geometry.boundingBox.isEmpty()) {
- objectBox.copy(geometry.boundingBox)
- objectBox.applyMatrix4(toBaseMatrix)
- box.union(objectBox)
- }
- }
- if (!(object instanceof Solid)) {
- const children = object.children
- for (let child of children) {
- const matrix = new THREE.Matrix4()
- matrix.copy(toBaseMatrix).multiply(child.matrix)
- extendBox(child, matrix)
- }
- }
- }
- }
- extendBox(object, new THREE.Matrix4())
- return box
- }
- static getBoxGeometry(box) {
- var size = new THREE.Vector3()
- box.getSize(size)
- var points = []
- var xmin = box.min.x
- var ymin = box.min.y
- var zmin = box.min.z
- var xmax = box.max.x
- var ymax = box.max.y
- var zmax = box.max.z
- var b0 = box.min
- var b1 = new THREE.Vector3(xmax, ymin, zmin)
- var b2 = new THREE.Vector3(xmax, ymax, zmin)
- var b3 = new THREE.Vector3(xmin, ymax, zmin)
- var t0 = new THREE.Vector3(xmin, ymin, zmax)
- var t1 = new THREE.Vector3(xmax, ymin, zmax)
- var t2 = box.max
- var t3 = new THREE.Vector3(xmin, ymax, zmax)
- points.push(b0)
- points.push(b1)
- points.push(b1)
- points.push(b2)
- points.push(b2)
- points.push(b3)
- points.push(b3)
- points.push(b0)
- points.push(t0)
- points.push(t1)
- points.push(t1)
- points.push(t2)
- points.push(t2)
- points.push(t3)
- points.push(t3)
- points.push(t0)
- points.push(b0)
- points.push(t0)
- points.push(b1)
- points.push(t1)
- points.push(b2)
- points.push(t2)
- points.push(b3)
- points.push(t3)
- return new THREE.BufferGeometry().setFromPoints(points)
- }
- static findCameras(object, array) {
- if (array === undefined) array = []
- if (object instanceof THREE.Camera) {
- array.push(object)
- }
- var children = object.children
- for (var i = 0; i < children.length; i++) {
- this.findCameras(children[i], array)
- }
- return array
- }
- static findMaterials(object, array) {
- if (array === undefined) array = []
- var materialMap = {}
- object.traverse(function(object) {
- var material = object.material
- if (material) {
- if (materialMap[material.uuid] === undefined) {
- materialMap[material.uuid] = material
- array.push(material)
- }
- }
- })
- return array
- }
- static isObjectDescendantOf(object, parent) {
- object = object.parent
- while (object !== null && object !== parent) {
- object = object.parent
- }
- return object === parent
- }
- static isExportable(object) {
- if (object.name && object.name.startsWith(THREE.Object3D.HIDDEN_PREFIX)) return false // hidden object
- const exportInfo = object.userData.export
- if (exportInfo) {
- if (exportInfo.export === false) {
- // marked as non exportable
- return false
- }
- }
- return true
- }
- static isExportableChildren(object) {
- const exportInfo = object.userData.export
- if (exportInfo) {
- if (exportInfo.exportChildren === false) {
- // children non exportable
- return false
- }
- }
- return true
- }
- static scaleModel(model, toUnits = 'm', fromUnits) {
- fromUnits = fromUnits || model.userData.units
- if (fromUnits) {
- let factor1 = this.METER_CONVERSION_FACTORS[toUnits]
- let factor2 = this.METER_CONVERSION_FACTORS[fromUnits]
- if (factor1 !== undefined && factor2 !== undefined) {
- let scale = factor1 / factor2
- model.scale.set(scale, scale, scale)
- model.updateMatrix()
- return true
- }
- }
- return false
- }
- }
- export { ObjectUtils }
|