GeometryUtils.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. /**
  2. * GeometryUtils.js
  3. *
  4. * @author realor
  5. */
  6. import * as THREE from '../lib/three.module.js'
  7. class GeometryUtils {
  8. static PRECISION = 0.00001
  9. static _vector1 = new THREE.Vector3()
  10. static _vector2 = new THREE.Vector3()
  11. static _vector3 = new THREE.Vector3()
  12. static _vector4 = new THREE.Vector3()
  13. static _vector5 = new THREE.Vector3()
  14. static _plane1 = new THREE.Plane()
  15. static isPointOnSegment(point, pointA, pointB, distance = 0.0001) {
  16. const projectedPoint = this._vector1
  17. if (this.projectPointOnSegment(point, pointA, pointB, projectedPoint)) {
  18. return projectedPoint.distanceToSquared(point) < distance * distance
  19. }
  20. return false
  21. }
  22. static projectPointOnSegment(point, pointA, pointB, projectedPoint) {
  23. const vAB = this._vector2
  24. const vAP = this._vector3
  25. const vProjAB = this._vector4
  26. vAB.subVectors(pointB, pointA)
  27. vAP.subVectors(point, pointA)
  28. const denominator = vAB.lengthSq()
  29. if (denominator === 0) return null
  30. const scalar = vAB.dot(vAP) / denominator
  31. if (scalar >= 0 && scalar <= 1) {
  32. vProjAB.copy(vAB).multiplyScalar(scalar)
  33. if (!(projectedPoint instanceof THREE.Vector3)) {
  34. projectedPoint = new THREE.Vector3()
  35. }
  36. projectedPoint.copy(pointA).add(vProjAB)
  37. return projectedPoint
  38. }
  39. return null
  40. }
  41. static intersectLines(line1, line2, position1, position2) {
  42. const vector1 = GeometryUtils._vector1
  43. const vector2 = GeometryUtils._vector2
  44. const normal = GeometryUtils._vector5
  45. const plane = GeometryUtils._plane1
  46. position1 = position1 || GeometryUtils._vector3
  47. position2 = position2 || GeometryUtils._vector4
  48. vector1.subVectors(line1.end, line1.start).normalize()
  49. vector2.subVectors(line2.end, line2.start).normalize()
  50. if (Math.abs(vector1.dot(vector2)) < 0.9999) {
  51. // are not parallel
  52. normal
  53. .copy(vector1)
  54. .cross(vector2)
  55. .normalize()
  56. vector1.cross(normal).normalize()
  57. plane.setFromNormalAndCoplanarPoint(vector1, line1.start)
  58. if (plane.intersectLine(line2, position2)) {
  59. vector2.cross(normal).normalize()
  60. plane.setFromNormalAndCoplanarPoint(vector2, line2.start)
  61. if (plane.intersectLine(line1, position1)) {
  62. return position1.distanceTo(position2)
  63. }
  64. }
  65. }
  66. return -1
  67. }
  68. static centroid(vertexPositions, accessFn, centroid) {
  69. if (!(centroid instanceof THREE.Vector3)) centroid = new THREE.Vector3()
  70. else centroid.set(0, 0, 0)
  71. const count = vertexPositions.length
  72. let point
  73. for (let i = 0; i < count; i++) {
  74. if (accessFn) {
  75. point = accessFn(vertexPositions[i])
  76. } else {
  77. point = vertexPositions[i]
  78. }
  79. centroid.x += point.x
  80. centroid.y += point.y
  81. centroid.z += point.z
  82. }
  83. centroid.x /= count
  84. centroid.y /= count
  85. centroid.z /= count
  86. return centroid
  87. }
  88. static calculateNormal(vertexPositions, accessFn, normal) {
  89. if (!(normal instanceof THREE.Vector3)) normal = new THREE.Vector3()
  90. else normal.set(0, 0, 0)
  91. // Newell's method
  92. const count = vertexPositions.length
  93. let pi, pj
  94. for (let i = 0; i < count; i++) {
  95. let j = (i + 1) % count
  96. if (accessFn) {
  97. pi = accessFn(vertexPositions[i])
  98. pj = accessFn(vertexPositions[j])
  99. } else {
  100. pi = vertexPositions[i]
  101. pj = vertexPositions[j]
  102. }
  103. normal.x += (pi.y - pj.y) * (pi.z + pj.z)
  104. normal.y += (pi.z - pj.z) * (pi.x + pj.x)
  105. normal.z += (pi.x - pj.x) * (pi.y + pj.y)
  106. }
  107. normal.normalize()
  108. return normal
  109. }
  110. /* triangulate a 3D face */
  111. static triangulateFace(vertices, holes, normal) {
  112. const vx = GeometryUtils._vector1
  113. const vy = GeometryUtils._vector2
  114. const vz = GeometryUtils._vector3
  115. if (normal instanceof THREE.Vector3) {
  116. vz.copy(normal)
  117. } else {
  118. GeometryUtils.calculateNormal(vertices, undefined, vz)
  119. }
  120. const v0 = vertices[0]
  121. const v1 = vertices[1]
  122. vx.subVectors(v1, v0).normalize()
  123. vy.crossVectors(vz, vx)
  124. const matrix = new THREE.Matrix4()
  125. matrix.set(vx.x, vy.x, vz.x, v0.x, vx.y, vy.y, vz.y, v0.y, vx.z, vy.z, vz.z, v0.z, 0, 0, 0, 1).invert()
  126. const projectVertices = vertices => {
  127. let projectedVertices = []
  128. for (let vertex of vertices) {
  129. let point = new THREE.Vector3()
  130. point.copy(vertex)
  131. projectedVertices.push(point.applyMatrix4(matrix))
  132. }
  133. return projectedVertices
  134. }
  135. let projectedVertices = projectVertices(vertices)
  136. let projectedHoles = []
  137. for (let hole of holes) {
  138. projectedHoles.push(projectVertices(hole))
  139. }
  140. return THREE.ShapeUtils.triangulateShape(projectedVertices, projectedHoles)
  141. }
  142. static intersectLinePlane(v1, v2, plane) {
  143. let v21 = v2.clone().sub(v1)
  144. let t = -(plane.normal.dot(v1) + plane.constant) / plane.normal.dot(v21)
  145. return v21.multiplyScalar(t).add(v1)
  146. }
  147. static orthogonalVector(vector, orthoVector) {
  148. if (!(orthoVector instanceof THREE.Vector3)) {
  149. orthoVector = new THREE.Vector3()
  150. }
  151. if (Math.abs(vector.x) > 0.1) {
  152. orthoVector.set(vector.y, -vector.x, vector.z)
  153. } else if (Math.abs(vector.y) > 0.1) {
  154. orthoVector.set(-vector.y, vector.x, vector.z)
  155. } // (~0, ~0, z)
  156. else {
  157. orthoVector.set(-vector.z, vector.y, vector.x)
  158. }
  159. return orthoVector.cross(vector)
  160. }
  161. static traverseBufferGeometryVertices(geometry, callback) {
  162. const position = this._vector1
  163. const positions = geometry.attributes.position.array
  164. for (let i = 0; i < positions.length; i += 3) {
  165. position.x = positions[i]
  166. position.y = positions[i + 1]
  167. position.z = positions[i + 2]
  168. callback(position)
  169. }
  170. }
  171. static getBufferGeometryVertices(geometry) {
  172. const positions = geometry.attributes.position.array
  173. const vertices = []
  174. for (let i = 0; i < positions.length; i += 3) {
  175. let x = positions[i]
  176. let y = positions[i + 1]
  177. let z = positions[i + 2]
  178. vertices.push(new THREE.Vector3(x, y, z))
  179. }
  180. return vertices
  181. }
  182. static getBufferGeometryFaces(geometry, addFace) {
  183. const positions = geometry.attributes.position.array
  184. if (geometry.index) {
  185. // indexed geometry
  186. let indices = geometry.index.array
  187. for (let i = 0; i < indices.length; i += 3) {
  188. let va = indices[i]
  189. let vb = indices[i + 1]
  190. let vc = indices[i + 2]
  191. addFace(va, vb, vc)
  192. }
  193. } // non indexed geometry
  194. else {
  195. var vertexCount = positions.length / 3
  196. for (let i = 0; i < vertexCount; i += 3) {
  197. let va = i
  198. let vb = i + 1
  199. let vc = i + 2
  200. addFace(va, vb, vc)
  201. }
  202. }
  203. }
  204. /**
  205. * Simplified version of BufferGeometryUtils.mergeBufferGeometries
  206. *
  207. * @param {Array<BufferGeometry>} geometries
  208. * @param {Boolean} useGroups
  209. * @return {BufferAttribute}
  210. */
  211. static mergeBufferGeometries(geometries, useGroups = false) {
  212. const isIndexed = geometries[0].index !== null
  213. const attributesUsed = new Set(Object.keys(geometries[0].attributes))
  214. const attributes = {}
  215. const mergedGeometry = new THREE.BufferGeometry()
  216. let offset = 0
  217. for (let i = 0; i < geometries.length; ++i) {
  218. const geometry = geometries[i]
  219. let attributesCount = 0
  220. // ensure that all geometries are indexed, or none
  221. if (isIndexed !== (geometry.index !== null)) {
  222. console.error('Not common attributes')
  223. return null
  224. }
  225. // gather attributes, exit early if they're different
  226. for (const name in geometry.attributes) {
  227. if (!attributesUsed.has(name)) {
  228. console.error('Not common attributes')
  229. return null
  230. }
  231. if (attributes[name] === undefined) {
  232. attributes[name] = []
  233. }
  234. attributes[name].push(geometry.attributes[name])
  235. attributesCount++
  236. }
  237. // ensure geometries have the same number of attributes
  238. if (attributesCount !== attributesUsed.size) {
  239. console.error('Not all geometries have the same number of attributes.')
  240. return null
  241. }
  242. // gather .userData
  243. mergedGeometry.userData.mergedUserData = mergedGeometry.userData.mergedUserData || []
  244. mergedGeometry.userData.mergedUserData.push(geometry.userData)
  245. if (useGroups) {
  246. let count
  247. if (isIndexed) {
  248. count = geometry.index.count
  249. } else if (geometry.attributes.position !== undefined) {
  250. count = geometry.attributes.position.count
  251. } else {
  252. console.error('Geometry has not an index or a position attribute')
  253. return null
  254. }
  255. mergedGeometry.addGroup(offset, count, i)
  256. offset += count
  257. }
  258. }
  259. // merge indices
  260. if (isIndexed) {
  261. let indexOffset = 0
  262. const mergedIndex = []
  263. for (let i = 0; i < geometries.length; ++i) {
  264. const index = geometries[i].index
  265. for (let j = 0; j < index.count; ++j) {
  266. mergedIndex.push(index.getX(j) + indexOffset)
  267. }
  268. indexOffset += geometries[i].attributes.position.count
  269. }
  270. mergedGeometry.setIndex(mergedIndex)
  271. }
  272. // merge attributes
  273. for (const name in attributes) {
  274. const mergedAttribute = this.mergeBufferAttributes(attributes[name])
  275. if (!mergedAttribute) {
  276. console.error('Failed while merging the ' + name + ' attribute.')
  277. return null
  278. }
  279. mergedGeometry.setAttribute(name, mergedAttribute)
  280. }
  281. return mergedGeometry
  282. }
  283. /**
  284. * @param {Array<BufferAttribute>} attributes
  285. * @return {BufferAttribute}
  286. */
  287. static mergeBufferAttributes(attributes) {
  288. let TypedArray
  289. let itemSize
  290. let normalized
  291. let arrayLength = 0
  292. for (let i = 0; i < attributes.length; ++i) {
  293. const attribute = attributes[i]
  294. if (attribute.isInterleavedBufferAttribute) {
  295. console.error('InterleavedBufferAttributes are not supported.')
  296. return null
  297. }
  298. if (TypedArray === undefined) {
  299. TypedArray = attribute.array.constructor
  300. }
  301. if (TypedArray !== attribute.array.constructor) {
  302. console.error('BufferAttribute.array is not consistent.')
  303. return null
  304. }
  305. if (itemSize === undefined) {
  306. itemSize = attribute.itemSize
  307. }
  308. if (itemSize !== attribute.itemSize) {
  309. console.error('BufferAttribute.itemSize is not consistent.')
  310. return null
  311. }
  312. if (normalized === undefined) {
  313. normalized = attribute.normalized
  314. }
  315. if (normalized !== attribute.normalized) {
  316. console.error('BufferAttribute.normalized is not consistent.')
  317. return null
  318. }
  319. arrayLength += attribute.array.length
  320. }
  321. const array = new TypedArray(arrayLength)
  322. let offset = 0
  323. for (let i = 0; i < attributes.length; ++i) {
  324. array.set(attributes[i].array, offset)
  325. offset += attributes[i].array.length
  326. }
  327. return new THREE.BufferAttribute(array, itemSize, normalized)
  328. }
  329. }
  330. export { GeometryUtils }