Extruder.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. /*
  2. * Extruder.js
  3. *
  4. * @author realor
  5. */
  6. import { ObjectBuilder } from './ObjectBuilder.js'
  7. import { SweptSolidBuilder } from './SweptSolidBuilder.js'
  8. import { Solid } from '../core/Solid.js'
  9. import { Profile } from '../core/Profile.js'
  10. import { Cord } from '../core/Cord.js'
  11. import { SolidGeometry } from '../core/SolidGeometry.js'
  12. import { ProfileGeometry } from '../core/ProfileGeometry.js'
  13. import * as THREE from '../lib/three.module.js'
  14. class Extruder extends SweptSolidBuilder {
  15. depth = 1
  16. direction = new THREE.Vector3(0, 0, 1)
  17. smoothAngle = 0
  18. constructor(depth, direction) {
  19. super()
  20. if (typeof depth === 'number') {
  21. this.depth = depth
  22. }
  23. if (direction instanceof THREE.Vector3) {
  24. this.direction.copy(direction)
  25. }
  26. }
  27. performBuild(solid) {
  28. const direction = this.direction
  29. const depth = this.depth
  30. let profile = this.findClosedProfile(solid)
  31. if (profile === undefined) return true
  32. let [outerRing, innerRings, stepVertexCount] = this.prepareRings(profile)
  33. const sign = Math.sign(depth) * Math.sign(direction.z)
  34. let cordPoints
  35. let extrudeVector = null
  36. let cord = this.findCord(solid)
  37. if (cord && cord.geometry) {
  38. cord.visible = false
  39. cordPoints = cord.geometry.points
  40. //remove duplicated vertices
  41. const array = []
  42. array.push(cordPoints[0])
  43. for (let i = 1; i < cordPoints.length; i++) {
  44. if (cordPoints[i - 1].distanceTo(cordPoints[i]) >= this.minPointDistance) {
  45. array.push(cordPoints[i].clone())
  46. }
  47. }
  48. if (array.length < 2) return
  49. cordPoints = array
  50. for (let point of cordPoints) {
  51. point.applyMatrix4(cord.matrix)
  52. }
  53. } else {
  54. // extrude in direction vector
  55. extrudeVector = new THREE.Vector3()
  56. extrudeVector.copy(direction).normalize()
  57. if (depth !== 0) {
  58. extrudeVector.multiplyScalar(Math.abs(depth) * Math.sign(direction.z))
  59. }
  60. // create cordPoints from direction
  61. cordPoints = [] // Point3D[]
  62. cordPoints.push(new THREE.Vector3(0, 0, 0))
  63. cordPoints.push(new THREE.Vector3(0, 0, extrudeVector.z))
  64. }
  65. const p1 = new THREE.Vector3()
  66. const p2 = new THREE.Vector3()
  67. const p3 = new THREE.Vector3()
  68. const vs = new THREE.Vector3()
  69. const vx = new THREE.Vector3()
  70. const vy = new THREE.Vector3()
  71. const vz = new THREE.Vector3()
  72. const v1 = new THREE.Vector3()
  73. const v2 = new THREE.Vector3()
  74. const ray = new THREE.Ray()
  75. // add fake point to generate last ring
  76. let length = cordPoints.length
  77. vs.subVectors(cordPoints[length - 1], cordPoints[length - 2])
  78. let last = new THREE.Vector3()
  79. last.copy(cordPoints[length - 1]).add(vs)
  80. cordPoints.push(last)
  81. // create matrix from initial segment of cordPoints
  82. p1.copy(cordPoints[0])
  83. p2.copy(cordPoints[1])
  84. vz.subVectors(p2, p1)
  85. vz.normalize()
  86. if (vz.y !== 0) vx.set(-vz.y, vz.x, 0).normalize()
  87. else if (vz.x !== 0) vx.set(vz.y, -vz.x, 0).normalize()
  88. else vx.set(1, 0, 0)
  89. vy.crossVectors(vz, vx)
  90. let matrix = new THREE.Matrix4()
  91. matrix.set(vx.x, vy.x, vz.x, p1.x, vx.y, vy.y, vz.y, p1.y, vx.z, vy.z, vz.z, p1.z, 0, 0, 0, 1)
  92. matrix.multiply(profile.matrix)
  93. const geometry = new SolidGeometry()
  94. const getPlane = (v1, v2, point) => {
  95. let normal = new THREE.Vector3()
  96. if (Math.abs(v1.dot(v2)) > 0.9999) {
  97. normal = v1
  98. } else {
  99. let s = new THREE.Vector3()
  100. s.subVectors(v2, v1).normalize()
  101. let v = new THREE.Vector3()
  102. v.crossVectors(s, v1).normalize()
  103. normal.crossVectors(s, v).normalize()
  104. }
  105. let plane = new THREE.Plane()
  106. plane.setFromNormalAndCoplanarPoint(normal, point)
  107. return plane
  108. }
  109. // add all ring vertices
  110. this.addStepVertices(outerRing, innerRings, matrix, geometry)
  111. if (depth === 0) {
  112. // special case
  113. // add top face
  114. this.addProfileFace(0, outerRing, innerRings, false, geometry)
  115. geometry.isManifold = false
  116. geometry.smoothAngle = this.smoothAngle
  117. solid.updateGeometry(geometry)
  118. return true
  119. }
  120. // add bottom face
  121. this.addProfileFace(0, outerRing, innerRings, true, geometry)
  122. let offset1 = 0
  123. let offset2 = stepVertexCount
  124. // create side faces
  125. for (let i = 1; i < cordPoints.length - 1; i++) {
  126. // add new step vertices
  127. p1.copy(cordPoints[i - 1])
  128. p2.copy(cordPoints[i])
  129. p3.copy(cordPoints[i + 1])
  130. v1.subVectors(p2, p1).normalize()
  131. v2.subVectors(p3, p2).normalize()
  132. let plane = getPlane(v1, v2, p2)
  133. for (let i = 0; i < stepVertexCount; i++) {
  134. ray.set(geometry.vertices[offset1 + i], v1)
  135. let vertex = new THREE.Vector3()
  136. vertex = ray.intersectPlane(plane, vertex)
  137. if (vertex === null) throw "Can't extrude this profile for the given directrix"
  138. geometry.vertices.push(vertex)
  139. }
  140. this.addLateralFaces(offset1, offset2, outerRing, innerRings, false, geometry)
  141. offset1 = offset2
  142. offset2 += stepVertexCount
  143. }
  144. // add top face
  145. this.addProfileFace(offset1, outerRing, innerRings, false, geometry)
  146. if (extrudeVector) {
  147. /* shear */
  148. const a = extrudeVector.x / extrudeVector.z
  149. const b = extrudeVector.y / extrudeVector.z
  150. const shearMatrix = new THREE.Matrix4()
  151. shearMatrix.elements[8] = a
  152. shearMatrix.elements[9] = b
  153. if (sign < 0) {
  154. const reverseMatrix = new THREE.Matrix4()
  155. extrudeVector.multiplyScalar(-1)
  156. reverseMatrix.makeTranslation(extrudeVector.x, extrudeVector.y, extrudeVector.z)
  157. shearMatrix.multiplyMatrices(reverseMatrix, shearMatrix)
  158. }
  159. geometry.applyMatrix4(shearMatrix)
  160. }
  161. geometry.isManifold = true
  162. geometry.smoothAngle = this.smoothAngle
  163. solid.updateGeometry(geometry)
  164. return true
  165. }
  166. findClosedProfile(solid) {
  167. for (let child of solid.children) {
  168. if (child instanceof Profile) {
  169. if (child.geometry && child.geometry.isClosed()) return child
  170. }
  171. }
  172. return undefined
  173. }
  174. findCord(solid) {
  175. for (let child of solid.children) {
  176. if (child instanceof Cord) {
  177. if (child.geometry) return child
  178. }
  179. }
  180. return undefined
  181. }
  182. copy(source) {
  183. this.depth = source.depth
  184. this.direction.copy(source.direction)
  185. this.smoothAngle = source.smothAngle // degrees
  186. this.minPointDistance = source.minPointDistance
  187. return this
  188. }
  189. }
  190. ObjectBuilder.addClass(Extruder)
  191. export { Extruder }