ObjectUtils.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  1. /**
  2. * ObjectUtils.js
  3. *
  4. * @author realor
  5. */
  6. import { Cord } from '../core/Cord.js'
  7. import { Profile } from '../core/Profile.js'
  8. import { Solid } from '../core/Solid.js'
  9. import { SolidGeometry } from '../core/SolidGeometry.js'
  10. import * as THREE from '../lib/three.module.js'
  11. class ObjectUtils {
  12. static ORIGINAL_MATERIAL = 'originalMaterial'
  13. static METER_CONVERSION_FACTORS = {
  14. km: 0.001,
  15. m: 1,
  16. cm: 100,
  17. mm: 1000,
  18. in: 39.3701
  19. }
  20. static getObjectValue(object, ...properties) {
  21. if (properties.length === 0) return object
  22. let data = object
  23. let i = 0
  24. while (i < properties.length && data && typeof data === 'object') {
  25. let property = properties[i]
  26. if (data instanceof THREE.Object3D) {
  27. if (typeof property === 'string') {
  28. if (property.startsWith('child:')) {
  29. let childName = property.substring(6)
  30. let children = data.children
  31. data = null
  32. for (let child of children) {
  33. if (child.name === childName) {
  34. data = child
  35. break
  36. }
  37. }
  38. } else if (property.startsWith('descendant:')) {
  39. let descendantName = property.substring(11)
  40. let children = data.children
  41. for (let child of children) {
  42. data = child.getObjectByName(descendantName)
  43. if (data) break
  44. }
  45. } else if (property.startsWith('ancestor:')) {
  46. let ancestorName = property.substring(9)
  47. let ancestor = data.parent
  48. while (ancestor !== null && ancestor.name !== ancestorName) {
  49. ancestor = ancestor.parent
  50. }
  51. data = ancestor
  52. } else {
  53. let value = data[property]
  54. data = value === undefined ? data.userData[property] : value
  55. }
  56. } else if (typeof property === 'number') {
  57. data = data.children[property]
  58. } else {
  59. data = undefined
  60. }
  61. } else {
  62. data = data[property]
  63. }
  64. i++
  65. }
  66. if (data === null || data === undefined) data = ''
  67. return data
  68. }
  69. /**
  70. * Creates a function to evaluate a expression for an object
  71. *
  72. * @param {String|Function} expression : an expression with references to
  73. * $ function. When expression is a String it can also have references to
  74. * these object properties: object, position, rotation, scale, material,
  75. * userData, builder and controllers.
  76. *
  77. * @returns {Function} : function fn(object) that evaluates expression for
  78. * the given object.
  79. *
  80. * Examples:
  81. * ObjectUtils.createEvalFunction('$("IFC", "ifcClassName") === "IfcBeam"')
  82. * ObjectUtils.createEvalFunction($ => $("IFC", "Name") === "House")
  83. *
  84. */
  85. static createEvalFunction(expression) {
  86. if (typeof expression === 'string') {
  87. let fn = new Function('object', 'position', 'rotation', 'scale', 'material', 'userData', 'builder', 'controllers', '$', 'return ' + expression + ';')
  88. return object => {
  89. const $ = (...properties) => this.getObjectValue(object, ...properties)
  90. return fn(object, object.position, object.rotation, object.scale, object.material, object.userData, object.builder, object.controllers, $)
  91. }
  92. } else if (typeof expression === 'function') {
  93. return object => {
  94. const $ = (...properties) => this.getObjectValue(object, ...properties)
  95. return expression($)
  96. }
  97. } else {
  98. return () => expression
  99. }
  100. }
  101. static dispose(root, geometries = true, materials = true) {
  102. root.traverse(object => {
  103. if (geometries && object.geometry) {
  104. const geometry = object.geometry
  105. if (geometry.dispose) {
  106. geometry.dispose()
  107. }
  108. }
  109. if (materials && object.material) {
  110. this.disposeMaterial(object.material)
  111. }
  112. })
  113. }
  114. static disposeMaterial(material) {
  115. if (material instanceof THREE.Material) {
  116. material.dispose()
  117. } else if (material instanceof Array) {
  118. for (let mat of material) {
  119. mat.dispose()
  120. }
  121. }
  122. }
  123. static applyMaterial(object, material, original = true) {
  124. const ORIGINAL_MATERIAL = ObjectUtils.ORIGINAL_MATERIAL
  125. let changed = false
  126. if (material === null) {
  127. // restore original material if possible
  128. if (object[ORIGINAL_MATERIAL]) {
  129. this.disposeMaterial(object.material)
  130. object.material = object[ORIGINAL_MATERIAL]
  131. object[ORIGINAL_MATERIAL] = null
  132. changed = true
  133. }
  134. } else if (material instanceof THREE.Material) {
  135. if (original) {
  136. if (object[ORIGINAL_MATERIAL]) {
  137. this.disposeMaterial(object[ORIGINAL_MATERIAL])
  138. object[ORIGINAL_MATERIAL] = null
  139. }
  140. } else {
  141. if (object[ORIGINAL_MATERIAL]) {
  142. // original material already saved
  143. } else {
  144. object[ORIGINAL_MATERIAL] = object.material
  145. }
  146. }
  147. if (object.material !== material) {
  148. this.disposeMaterial(object.material)
  149. object.material = material
  150. changed = true
  151. }
  152. }
  153. return changed
  154. }
  155. static findObjects(condition, baseObject, nested = false) {
  156. const objects = []
  157. function traverse(object) {
  158. let accepted = condition(object)
  159. if (accepted) {
  160. objects.push(object)
  161. }
  162. if (!accepted || nested) {
  163. if (!(object instanceof Solid)) {
  164. const children = object.children
  165. for (let child of children) {
  166. traverse(child)
  167. }
  168. }
  169. }
  170. }
  171. traverse(baseObject)
  172. return objects
  173. }
  174. static updateVisibility(objects, visible) {
  175. return this.updateAppearance(objects, { visible: visible })
  176. }
  177. static updateStyle(objects, edgesVisible = true, facesVisible = true) {
  178. return this.updateAppearance(objects, { edgesVisible: edgesVisible, facesVisible: facesVisible })
  179. }
  180. static updateAppearance(objects, appearance) {
  181. const visited = appearance.visible ? new Set() : null
  182. const setupMaterial = (property, materialClass) => {
  183. let material = appearance[property]
  184. if (material instanceof THREE.Material || material === null) return material
  185. if (typeof material === 'object') {
  186. return new materialClass(material)
  187. }
  188. return undefined
  189. }
  190. let meshMaterial = setupMaterial('meshMaterial', THREE.MeshPhongMaterial)
  191. let lineMaterial = setupMaterial('lineMaterial', THREE.LineBasicMaterial)
  192. let pointsMaterial = setupMaterial('pointsMaterial', THREE.PointsMaterial)
  193. return ObjectUtils.updateObjects(
  194. objects,
  195. (object, changed) => {
  196. if (appearance.visible !== undefined) {
  197. if (object.visible !== appearance.visible) {
  198. object.visible = appearance.visible
  199. changed.add(object)
  200. if (object.visible) {
  201. // make ancestors visible
  202. let ancestor = object.parent
  203. while (ancestor !== null && !visited.has(ancestor)) {
  204. if (ancestor.visible === false) {
  205. ancestor.visible = true
  206. changed.add(ancestor)
  207. }
  208. visited.add(ancestor)
  209. ancestor = ancestor.parent
  210. }
  211. }
  212. }
  213. }
  214. const original = appearance.original === true
  215. if (object instanceof THREE.Mesh) {
  216. if (ObjectUtils.applyMaterial(object, meshMaterial, original)) {
  217. changed.add(object)
  218. }
  219. } else if (object instanceof THREE.Line) {
  220. if (ObjectUtils.applyMaterial(object, lineMaterial, original)) {
  221. changed.add(object)
  222. }
  223. } else if (object instanceof THREE.Points) {
  224. if (ObjectUtils.applyMaterial(object, pointsMaterial, original)) {
  225. changed.add(object)
  226. }
  227. } else if (object instanceof Solid) {
  228. if (appearance.facesVisible !== undefined) {
  229. if (object.facesVisible !== appearance.facesVisible) {
  230. object.facesVisible = appearance.facesVisible
  231. changed.add(object)
  232. }
  233. }
  234. if (appearance.edgesVisible !== undefined) {
  235. if (object.edgesVisible !== appearance.edgesVisible) {
  236. object.edgesVisible = appearance.edgesVisible
  237. changed.add(object)
  238. }
  239. }
  240. if (ObjectUtils.applyMaterial(object.facesObject, meshMaterial, original)) {
  241. changed.add(object)
  242. }
  243. if (ObjectUtils.applyMaterial(object.edgesObject, lineMaterial, original)) {
  244. changed.add(object)
  245. }
  246. } else if (object instanceof Profile) {
  247. if (ObjectUtils.applyMaterial(object, lineMaterial, original)) {
  248. changed.add(object)
  249. }
  250. } else if (object instanceof Cord) {
  251. if (ObjectUtils.applyMaterial(object, lineMaterial, original)) {
  252. changed.add(object)
  253. }
  254. }
  255. },
  256. true
  257. )
  258. }
  259. static updateObjects(objects, updateFunction, recursive = false) {
  260. const changed = new Set()
  261. if (objects instanceof THREE.Object3D) {
  262. objects = [objects]
  263. }
  264. function traverse(object) {
  265. updateFunction(object, changed)
  266. if (
  267. !(object instanceof Solid) &&
  268. !(object instanceof Profile) &&
  269. !(object instanceof Cord) &&
  270. !(object instanceof THREE.Mesh) &&
  271. !(object instanceof THREE.Line) &&
  272. !(object instanceof THREE.Points)
  273. ) {
  274. const children = object.children
  275. for (let child of children) {
  276. traverse(child)
  277. }
  278. }
  279. }
  280. if (recursive) {
  281. for (let object of objects) {
  282. traverse(object)
  283. }
  284. } else {
  285. for (let object of objects) {
  286. updateFunction(object, changed)
  287. }
  288. }
  289. return changed
  290. }
  291. static zoomAll(camera, objects, aspect, all) {
  292. if (objects instanceof THREE.Object3D) {
  293. objects = [objects]
  294. }
  295. let box = ObjectUtils.getBoundingBoxFromView(objects, camera.matrixWorld, all) // box in camera CS
  296. if (box.isEmpty()) return
  297. let center = new THREE.Vector3()
  298. center = box.getCenter(center) // center in camera CS
  299. let matrix = camera.matrix
  300. center.applyMatrix4(camera.matrix) // center in camera parent CS
  301. let boxWidth = box.max.x - box.min.x
  302. let boxHeight = box.max.y - box.min.y
  303. let boxDepth = box.max.z - box.min.z
  304. let offset
  305. if (camera instanceof THREE.PerspectiveCamera) {
  306. let ymax = camera.near * Math.tan(THREE.MathUtils.degToRad(camera.fov * 0.5))
  307. let xmax = ymax * camera.aspect
  308. let yoffset = (boxHeight * camera.near) / (2 * ymax)
  309. let xoffset = (boxWidth * camera.near) / (2 * xmax)
  310. offset = Math.max(xoffset, yoffset) + 0.5 * boxDepth
  311. } // Ortho camera
  312. else {
  313. let factor = 0.5 * 1.1 // 10% extra space
  314. camera.left = -factor * boxWidth
  315. camera.right = factor * boxWidth
  316. camera.top = factor * boxHeight
  317. camera.bottom = -factor * boxHeight
  318. offset = camera.far - boxDepth
  319. }
  320. let v = new THREE.Vector3()
  321. v.setFromMatrixColumn(matrix, 2) // view vector (zaxis) in parent CS
  322. v.normalize()
  323. v.multiplyScalar(offset)
  324. center.add(v)
  325. camera.zoom = 1
  326. camera.position.copy(center)
  327. camera.updateMatrix()
  328. this.updateCameraAspectRatio(camera, aspect)
  329. }
  330. static updateCameraAspectRatio(camera, aspect) {
  331. if (camera instanceof THREE.PerspectiveCamera) {
  332. camera.aspect = aspect
  333. camera.updateProjectionMatrix()
  334. } else if (camera instanceof THREE.OrthographicCamera) {
  335. var width = camera.right - camera.left
  336. var height = camera.top - camera.bottom
  337. var currentAspect = width / height
  338. if (aspect < currentAspect) {
  339. var h = 0.5 * (width / aspect - height)
  340. camera.top += h
  341. camera.bottom -= h
  342. } else {
  343. var w = 0.5 * (height * aspect - width)
  344. camera.left -= w
  345. camera.right += w
  346. }
  347. camera.updateProjectionMatrix()
  348. }
  349. }
  350. static getBoundingBoxFromView(objects, viewMatrixWorld, all = false) {
  351. const box = new THREE.Box3() // empty box
  352. const vertex = new THREE.Vector3()
  353. const inverseMatrix = new THREE.Matrix4()
  354. inverseMatrix.copy(viewMatrixWorld).invert()
  355. function extendBox(object) {
  356. let geometry = object.geometry
  357. if (geometry) {
  358. if (geometry instanceof SolidGeometry) {
  359. let vertices = geometry.vertices
  360. for (let j = 0; j < vertices.length; j++) {
  361. vertex.copy(vertices[j])
  362. vertex.applyMatrix4(object.matrixWorld) // world CS
  363. vertex.applyMatrix4(inverseMatrix) // view CS
  364. box.expandByPoint(vertex)
  365. }
  366. } else if (geometry instanceof THREE.BufferGeometry) {
  367. let position = geometry.attributes.position
  368. if (position) {
  369. const positions = position.array
  370. for (let j = 0; j < positions.length; j += 3) {
  371. vertex.set(positions[j], positions[j + 1], positions[j + 2])
  372. vertex.applyMatrix4(object.matrixWorld) // world CS
  373. vertex.applyMatrix4(inverseMatrix) // view CS
  374. box.expandByPoint(vertex)
  375. }
  376. }
  377. }
  378. }
  379. }
  380. function traverse(object) {
  381. if (object.visible || all) {
  382. extendBox(object)
  383. if (!(object instanceof Solid || object instanceof Cord || object instanceof Profile)) {
  384. for (let child of object.children) {
  385. traverse(child)
  386. }
  387. }
  388. }
  389. }
  390. for (let object of objects) {
  391. traverse(object)
  392. }
  393. return box
  394. }
  395. static getLocalBoundingBox(object, all = false) {
  396. const box = new THREE.Box3() // empty box
  397. const objectBox = new THREE.Box3()
  398. function extendBox(object, toBaseMatrix) {
  399. if (object.visible || all) {
  400. let geometry = object.geometry
  401. if (geometry) {
  402. if (geometry.boundingBox === null) {
  403. geometry.computeBoundingBox()
  404. }
  405. if (!geometry.boundingBox.isEmpty()) {
  406. objectBox.copy(geometry.boundingBox)
  407. objectBox.applyMatrix4(toBaseMatrix)
  408. box.union(objectBox)
  409. }
  410. }
  411. if (!(object instanceof Solid)) {
  412. const children = object.children
  413. for (let child of children) {
  414. const matrix = new THREE.Matrix4()
  415. matrix.copy(toBaseMatrix).multiply(child.matrix)
  416. extendBox(child, matrix)
  417. }
  418. }
  419. }
  420. }
  421. extendBox(object, new THREE.Matrix4())
  422. return box
  423. }
  424. static getBoxGeometry(box) {
  425. var size = new THREE.Vector3()
  426. box.getSize(size)
  427. var points = []
  428. var xmin = box.min.x
  429. var ymin = box.min.y
  430. var zmin = box.min.z
  431. var xmax = box.max.x
  432. var ymax = box.max.y
  433. var zmax = box.max.z
  434. var b0 = box.min
  435. var b1 = new THREE.Vector3(xmax, ymin, zmin)
  436. var b2 = new THREE.Vector3(xmax, ymax, zmin)
  437. var b3 = new THREE.Vector3(xmin, ymax, zmin)
  438. var t0 = new THREE.Vector3(xmin, ymin, zmax)
  439. var t1 = new THREE.Vector3(xmax, ymin, zmax)
  440. var t2 = box.max
  441. var t3 = new THREE.Vector3(xmin, ymax, zmax)
  442. points.push(b0)
  443. points.push(b1)
  444. points.push(b1)
  445. points.push(b2)
  446. points.push(b2)
  447. points.push(b3)
  448. points.push(b3)
  449. points.push(b0)
  450. points.push(t0)
  451. points.push(t1)
  452. points.push(t1)
  453. points.push(t2)
  454. points.push(t2)
  455. points.push(t3)
  456. points.push(t3)
  457. points.push(t0)
  458. points.push(b0)
  459. points.push(t0)
  460. points.push(b1)
  461. points.push(t1)
  462. points.push(b2)
  463. points.push(t2)
  464. points.push(b3)
  465. points.push(t3)
  466. return new THREE.BufferGeometry().setFromPoints(points)
  467. }
  468. static findCameras(object, array) {
  469. if (array === undefined) array = []
  470. if (object instanceof THREE.Camera) {
  471. array.push(object)
  472. }
  473. var children = object.children
  474. for (var i = 0; i < children.length; i++) {
  475. this.findCameras(children[i], array)
  476. }
  477. return array
  478. }
  479. static findMaterials(object, array) {
  480. if (array === undefined) array = []
  481. var materialMap = {}
  482. object.traverse(function(object) {
  483. var material = object.material
  484. if (material) {
  485. if (materialMap[material.uuid] === undefined) {
  486. materialMap[material.uuid] = material
  487. array.push(material)
  488. }
  489. }
  490. })
  491. return array
  492. }
  493. static isObjectDescendantOf(object, parent) {
  494. object = object.parent
  495. while (object !== null && object !== parent) {
  496. object = object.parent
  497. }
  498. return object === parent
  499. }
  500. static isExportable(object) {
  501. if (object.name && object.name.startsWith(THREE.Object3D.HIDDEN_PREFIX)) return false // hidden object
  502. const exportInfo = object.userData.export
  503. if (exportInfo) {
  504. if (exportInfo.export === false) {
  505. // marked as non exportable
  506. return false
  507. }
  508. }
  509. return true
  510. }
  511. static isExportableChildren(object) {
  512. const exportInfo = object.userData.export
  513. if (exportInfo) {
  514. if (exportInfo.exportChildren === false) {
  515. // children non exportable
  516. return false
  517. }
  518. }
  519. return true
  520. }
  521. static scaleModel(model, toUnits = 'm', fromUnits) {
  522. fromUnits = fromUnits || model.userData.units
  523. if (fromUnits) {
  524. let factor1 = this.METER_CONVERSION_FACTORS[toUnits]
  525. let factor2 = this.METER_CONVERSION_FACTORS[fromUnits]
  526. if (factor1 !== undefined && factor2 !== undefined) {
  527. let scale = factor1 / factor2
  528. model.scale.set(scale, scale, scale)
  529. model.updateMatrix()
  530. return true
  531. }
  532. }
  533. return false
  534. }
  535. }
  536. export { ObjectUtils }