@jscad/modeling 3.0.0-alpha.0 → 3.0.2-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/LICENSE +1 -1
  3. package/dist/jscad-modeling.es.js +2 -2
  4. package/dist/jscad-modeling.min.js +2 -2
  5. package/package.json +2 -2
  6. package/rollup.config.js +1 -1
  7. package/src/colors/colorize.js +1 -5
  8. package/src/colors/colorize.test.js +8 -8
  9. package/src/geometries/geom2/transform.js +9 -1
  10. package/src/geometries/geom2/transform.test.js +57 -0
  11. package/src/geometries/geom3/fromPointsConvex.d.ts +4 -0
  12. package/src/geometries/geom3/fromPointsConvex.js +25 -0
  13. package/src/geometries/geom3/fromPointsConvex.test.js +32 -0
  14. package/src/geometries/geom3/index.d.ts +1 -0
  15. package/src/geometries/geom3/index.js +1 -0
  16. package/src/geometries/index.js +3 -4
  17. package/src/geometries/path2/appendBezier.js +1 -1
  18. package/src/geometries/poly3/index.js +1 -1
  19. package/src/geometries/poly3/measureBoundingBox.js +2 -0
  20. package/src/geometries/poly3/measureBoundingSphere.d.ts +2 -1
  21. package/src/geometries/poly3/measureBoundingSphere.js +25 -8
  22. package/src/geometries/poly3/measureBoundingSphere.test.js +12 -8
  23. package/src/geometries/poly3/type.d.ts +1 -1
  24. package/src/geometries/slice/validate.js +1 -2
  25. package/src/index.js +41 -0
  26. package/src/maths/index.js +1 -0
  27. package/src/maths/mat4/isOnlyTransformScale.js +1 -1
  28. package/src/measurements/measureAggregateArea.js +0 -1
  29. package/src/measurements/measureAggregateBoundingBox.js +0 -1
  30. package/src/measurements/measureAggregateEpsilon.js +0 -1
  31. package/src/measurements/measureAggregateVolume.js +0 -1
  32. package/src/measurements/measureArea.js +0 -1
  33. package/src/measurements/measureBoundingBox.js +0 -1
  34. package/src/measurements/measureBoundingSphere.js +2 -6
  35. package/src/measurements/measureEpsilon.js +0 -1
  36. package/src/measurements/measureVolume.js +0 -1
  37. package/src/operations/booleans/index.d.ts +1 -0
  38. package/src/operations/booleans/intersect.js +5 -5
  39. package/src/operations/booleans/intersect.test.js +6 -7
  40. package/src/operations/booleans/intersectGeom2.js +2 -6
  41. package/src/operations/booleans/intersectGeom2.test.js +25 -1
  42. package/src/operations/booleans/intersectGeom3.js +2 -6
  43. package/src/operations/booleans/intersectGeom3.test.js +5 -1
  44. package/src/operations/booleans/martinez/compareEvents.js +2 -7
  45. package/src/operations/booleans/martinez/connectEdges.js +30 -41
  46. package/src/operations/booleans/martinez/contour.js +1 -1
  47. package/src/operations/booleans/martinez/divideSegment.js +12 -11
  48. package/src/operations/booleans/martinez/fillQueue.js +24 -28
  49. package/src/operations/booleans/martinez/index.js +2 -1
  50. package/src/operations/booleans/martinez/possibleIntersection.js +41 -30
  51. package/src/operations/booleans/martinez/segmentIntersection.js +7 -9
  52. package/src/operations/booleans/martinez/splaytree.js +59 -457
  53. package/src/operations/booleans/martinez/subdivideSegments.js +4 -4
  54. package/src/operations/booleans/martinez/sweepEvent.js +3 -17
  55. package/src/operations/booleans/mayOverlap.js +0 -1
  56. package/src/operations/booleans/scission.d.ts +5 -0
  57. package/src/operations/booleans/scission.js +3 -5
  58. package/src/operations/booleans/scission.test.js +6 -0
  59. package/src/operations/booleans/subtract.js +5 -5
  60. package/src/operations/booleans/subtract.test.js +6 -7
  61. package/src/operations/booleans/subtractGeom2.js +2 -6
  62. package/src/operations/booleans/subtractGeom2.test.js +25 -1
  63. package/src/operations/booleans/subtractGeom3.js +2 -6
  64. package/src/operations/booleans/subtractGeom3.test.js +5 -1
  65. package/src/operations/booleans/trees/Node.js +25 -27
  66. package/src/operations/booleans/trees/PolygonTreeNode.js +153 -106
  67. package/src/operations/booleans/trees/Tree.js +9 -4
  68. package/src/operations/booleans/trees/splitLineSegmentByPlane.js +5 -3
  69. package/src/operations/booleans/trees/splitPolygonByPlane.d.ts +33 -0
  70. package/src/operations/booleans/trees/splitPolygonByPlane.js +39 -34
  71. package/src/operations/booleans/union.js +5 -5
  72. package/src/operations/booleans/union.test.js +6 -7
  73. package/src/operations/booleans/unionGeom2.js +2 -6
  74. package/src/operations/booleans/unionGeom2.test.js +25 -1
  75. package/src/operations/booleans/unionGeom3.js +2 -6
  76. package/src/operations/booleans/unionGeom3.test.js +6 -1
  77. package/src/operations/extrusions/extrudeFromSlices.test.js +8 -1
  78. package/src/operations/extrusions/extrudeHelical.js +2 -8
  79. package/src/operations/extrusions/extrudeLinear.js +1 -5
  80. package/src/operations/extrusions/extrudeLinear.test.js +7 -1
  81. package/src/operations/extrusions/extrudeRotate.js +3 -2
  82. package/src/operations/extrusions/extrudeRotate.test.js +13 -1
  83. package/src/operations/extrusions/extrudeWalls.js +3 -1
  84. package/src/operations/extrusions/project.js +1 -5
  85. package/src/operations/hulls/hull.js +6 -5
  86. package/src/operations/hulls/hull.test.js +56 -3
  87. package/src/operations/hulls/hullChain.js +11 -6
  88. package/src/operations/hulls/hullChain.test.js +12 -2
  89. package/src/operations/hulls/hullGeom2.js +5 -6
  90. package/src/operations/hulls/hullGeom3.js +9 -18
  91. package/src/operations/hulls/hullPath2.js +6 -7
  92. package/src/operations/hulls/hullPath2.test.js +1 -1
  93. package/src/operations/hulls/hullPoints2.d.ts +3 -0
  94. package/src/operations/hulls/hullPoints2.js +24 -30
  95. package/src/operations/hulls/hullPoints3.d.ts +4 -0
  96. package/src/operations/hulls/hullPoints3.js +21 -0
  97. package/src/operations/hulls/index.d.ts +2 -0
  98. package/src/operations/hulls/index.js +3 -1
  99. package/src/operations/modifiers/generalize.js +2 -6
  100. package/src/operations/modifiers/index.js +1 -1
  101. package/src/operations/modifiers/mergePolygons.js +2 -3
  102. package/src/operations/modifiers/reTesselateCoplanarPolygons.js +7 -7
  103. package/src/operations/modifiers/snap.js +2 -6
  104. package/src/operations/offsets/offset.js +1 -5
  105. package/src/operations/offsets/offsetFromPoints.test.js +0 -1
  106. package/src/operations/offsets/offsetGeom2.test.js +1 -0
  107. package/src/operations/offsets/offsetGeom3.js +0 -2
  108. package/src/operations/offsets/offsetGeom3.test.js +9 -1
  109. package/src/operations/offsets/offsetPath2.js +3 -3
  110. package/src/operations/transforms/align.js +8 -7
  111. package/src/operations/transforms/align.test.js +2 -2
  112. package/src/operations/transforms/center.js +6 -9
  113. package/src/operations/transforms/center.test.js +19 -1
  114. package/src/operations/transforms/mirror.js +5 -8
  115. package/src/operations/transforms/mirror.test.js +7 -7
  116. package/src/operations/transforms/rotate.js +5 -8
  117. package/src/operations/transforms/scale.js +5 -8
  118. package/src/operations/transforms/transform.js +2 -5
  119. package/src/operations/transforms/translate.js +5 -8
  120. package/src/primitives/arc.js +2 -0
  121. package/src/primitives/arc.test.js +11 -11
  122. package/src/primitives/circle.test.js +18 -8
  123. package/src/primitives/cube.test.js +10 -0
  124. package/src/primitives/cuboid.test.js +10 -0
  125. package/src/primitives/cylinder.test.js +12 -0
  126. package/src/primitives/cylinderElliptic.test.js +21 -1
  127. package/src/primitives/ellipse.test.js +18 -8
  128. package/src/primitives/ellipsoid.test.js +12 -0
  129. package/src/primitives/geodesicSphere.test.js +8 -0
  130. package/src/primitives/line.test.js +1 -1
  131. package/src/primitives/polygon.d.ts +1 -0
  132. package/src/primitives/polygon.js +13 -4
  133. package/src/primitives/polygon.test.js +15 -0
  134. package/src/primitives/polyhedron.js +1 -0
  135. package/src/primitives/polyhedron.test.js +8 -2
  136. package/src/primitives/rectangle.test.js +9 -3
  137. package/src/primitives/roundedCuboid.js +1 -1
  138. package/src/primitives/roundedCuboid.test.js +20 -4
  139. package/src/primitives/roundedCylinder.js +1 -1
  140. package/src/primitives/roundedCylinder.test.js +20 -0
  141. package/src/primitives/roundedRectangle.js +1 -1
  142. package/src/primitives/roundedRectangle.test.js +15 -6
  143. package/src/primitives/sphere.test.js +12 -0
  144. package/src/primitives/square.test.js +10 -4
  145. package/src/primitives/star.test.js +14 -6
  146. package/src/primitives/torus.js +1 -1
  147. package/src/primitives/torus.test.js +11 -1
  148. package/src/primitives/triangle.test.js +17 -9
  149. package/src/utils/coalesce.d.ts +3 -0
  150. package/src/utils/coalesce.js +20 -0
  151. package/src/utils/index.js +2 -2
  152. package/src/maths/mat4/leftMultiplyVec2.d.ts +0 -4
  153. package/src/maths/mat4/leftMultiplyVec2.js +0 -26
  154. package/src/maths/mat4/leftMultiplyVec3.d.ts +0 -4
  155. package/src/maths/mat4/leftMultiplyVec3.js +0 -27
  156. package/src/maths/mat4/mirror.d.ts +0 -4
  157. package/src/maths/mat4/mirror.js +0 -32
  158. package/src/maths/mat4/rightMultiplyVec2.d.ts +0 -4
  159. package/src/maths/mat4/rightMultiplyVec2.js +0 -27
  160. package/src/maths/mat4/rightMultiplyVec3.d.ts +0 -4
  161. package/src/maths/mat4/rightMultiplyVec3.js +0 -28
@@ -6,6 +6,9 @@ import * as poly3 from '../../../geometries/poly3/index.js'
6
6
 
7
7
  import { splitPolygonByPlane } from './splitPolygonByPlane.js'
8
8
 
9
+ // cached values to boost performance
10
+ const splitResult = { type: 0, front: null, back: null }
11
+
9
12
  // # class PolygonTreeNode
10
13
  // This class manages hierarchical splits of polygons.
11
14
  // At the top is a root node which does not hold a polygon, only child PolygonTreeNodes.
@@ -13,72 +16,72 @@ import { splitPolygonByPlane } from './splitPolygonByPlane.js'
13
16
  // The polygons can be in different planes.
14
17
  // splitByPlane() splits a node by a plane. If the plane intersects the polygon,
15
18
  // two new child nodes are created holding the split polygon.
16
- // getPolygons() retrieves the polygons from the tree. If for PolygonTreeNode the polygon is split but
19
+ // getPolygons() retrieves the polygons from the node. If for PolygonTreeNode the polygon is split but
17
20
  // the two split parts (child nodes) are still intact, then the unsplit polygon is returned.
18
21
  // This ensures that we can safely split a polygon into many fragments. If the fragments are untouched,
19
22
  // getPolygons() will return the original unsplit polygon instead of the fragments.
20
- // remove() removes a polygon from the tree. Once a polygon is removed, the parent polygons are invalidated
23
+ // remove() removes a polygon from the node. Once a polygon is removed, the parent polygons are invalidated
21
24
  // since they are no longer intact.
22
25
  export class PolygonTreeNode {
23
26
  // constructor creates the root node
24
27
  constructor (parent, polygon) {
25
28
  this.parent = parent
26
- this.children = []
27
29
  this.polygon = polygon
28
- this.removed = false // state of branch or leaf
30
+ this.children = []
29
31
  }
30
32
 
31
- // fill the tree with polygons. Should be called on the root node only; child nodes must
33
+ // fill the node with polygons. Should be called on the root node only; child nodes must
32
34
  // always be a derivate (split) of the parent node.
33
35
  addPolygons (polygons) {
34
36
  // new polygons can only be added to root node; children can only be split polygons
35
- if (!this.isRootNode()) {
36
- throw new Error('Assertion failed')
37
+ if (!this.isRootNode()) throw new Error('PolygonTreeNode01')
38
+
39
+ for (let i = 0; i < polygons.length; i++) {
40
+ this.addChild(polygons[i])
37
41
  }
38
- const _this = this
39
- polygons.forEach((polygon) => {
40
- _this.addChild(polygon)
41
- })
42
42
  }
43
43
 
44
44
  // remove a node
45
45
  // - the siblings become toplevel nodes
46
46
  // - the parent is removed recursively
47
47
  remove () {
48
- if (!this.removed) {
49
- this.removed = true
50
- this.polygon = null
48
+ this.polygon = null
51
49
 
52
- // remove ourselves from the parent's children list:
53
- const parentschildren = this.parent.children
54
- const i = parentschildren.indexOf(this)
55
- if (i < 0) throw new Error('Assertion failed')
56
- parentschildren.splice(i, 1)
50
+ // remove ourselves from the parent's children list:
51
+ const parentschildren = this.parent.children
52
+ const i = parentschildren.indexOf(this)
53
+ if (i < 0) throw new Error('PolyTreeNode02')
54
+ parentschildren.splice(i, 1)
57
55
 
58
- // invalidate the parent's polygon, and of all parents above it:
59
- this.parent.recursivelyInvalidatePolygon()
60
- }
56
+ // invalidate the parent's polygon, and of all parents above it:
57
+ this.parent._recursivelyInvalidatePolygon()
61
58
  }
62
59
 
63
- isRemoved () {
64
- return this.removed
60
+ /*
61
+ * Can the node be split, either base polygon or children
62
+ */
63
+ canSplit () {
64
+ return this.polygon != null || this.children.length > 0
65
65
  }
66
66
 
67
67
  isRootNode () {
68
68
  return !this.parent
69
69
  }
70
70
 
71
- // invert all polygons in the tree. Call on the root node
71
+ // invert all polygons in the node. Call on the root node
72
72
  invert () {
73
- if (!this.isRootNode()) throw new Error('Assertion failed') // can only call this on the root node
74
- this.invertSub()
73
+ if (!this.isRootNode()) throw new Error('PolyTreeNode03')
74
+ this._invertSub()
75
75
  }
76
76
 
77
77
  getPolygon () {
78
- if (!this.polygon) throw new Error('Assertion failed') // doesn't have a polygon, which means that it has been broken down
78
+ if (this.polygon === null) throw new Error('PolyTreeNode04')
79
79
  return this.polygon
80
80
  }
81
81
 
82
+ /*
83
+ * Get all polygons from the node, and add to the result
84
+ */
82
85
  getPolygons (result) {
83
86
  let children = [this]
84
87
  const queue = [children]
@@ -87,23 +90,37 @@ export class PolygonTreeNode {
87
90
  children = queue[i]
88
91
  for (j = 0, l = children.length; j < l; j++) { // ok to cache length
89
92
  node = children[j]
90
- if (node.polygon) {
91
- // the polygon hasn't been broken yet. We can ignore the children and return our polygon:
93
+ if (node.polygon !== null) {
94
+ // the polygon hasn't been broken yet. We can ignore the children and return our polygon
92
95
  result.push(node.polygon)
93
96
  } else {
94
- // our polygon has been split up and broken, so gather all subpolygons from the children
95
- if (node.children.length > 0) queue.push(node.children)
97
+ // our polygon has been split up and broken, so gather all subpolygons
98
+ if (node.children.length > 0) {
99
+ queue.push(node.children)
100
+ }
96
101
  }
97
102
  }
98
103
  }
99
104
  }
100
105
 
101
- // split the node by a plane; add the resulting nodes to the frontNodes and backNodes array
102
- // If the plane doesn't intersect the polygon, the 'this' object is added to one of the arrays
103
- // If the plane does intersect the polygon, two new child nodes are created for the front and back fragments,
104
- // and added to both arrays.
105
- splitByPlane (plane, coplanarFrontNodes, coplanarBackNodes, frontNodes, backNodes) {
106
- if (this.children.length) {
106
+ // NOTE: This version of getPolygons() is much SLOWER.
107
+ getPolygonsNew (result) {
108
+ if (this.polygon !== null) {
109
+ // the polygon hasn't been broken yet, so return the original polygon
110
+ result.push(this.polygon)
111
+ } else {
112
+ // the polygon has been split, so gather all polygons from the children
113
+ for (let i = 0; i < this.children.length; i++) {
114
+ const node = this.children[i]
115
+ node.getPolygons(result)
116
+ }
117
+ }
118
+ }
119
+
120
+ // split the node by a plane, adding the resulting nodes to the frontNodes and backNodes array
121
+ // Also see canSplit()
122
+ splitByPlaneOld (plane, coplanarfrontnodes, coplanarbacknodes, frontnodes, backnodes) {
123
+ if (this.children.length > 0) {
107
124
  const queue = [this.children]
108
125
  let i
109
126
  let j
@@ -117,68 +134,90 @@ export class PolygonTreeNode {
117
134
  if (node.children.length > 0) {
118
135
  queue.push(node.children)
119
136
  } else {
120
- // no children. Split the polygon:
121
- node._splitByPlane(plane, coplanarFrontNodes, coplanarBackNodes, frontNodes, backNodes)
137
+ if (this.polygon !== null) {
138
+ // no children. Split the polygon:
139
+ node._splitByPlane(plane, coplanarfrontnodes, coplanarbacknodes, frontnodes, backnodes)
140
+ }
122
141
  }
123
142
  }
124
143
  }
125
144
  } else {
126
- this._splitByPlane(plane, coplanarFrontNodes, coplanarBackNodes, frontNodes, backNodes)
145
+ if (this.polygon !== null) {
146
+ this._splitByPlane(plane, coplanarfrontnodes, coplanarbacknodes, frontnodes, backnodes)
147
+ }
148
+ }
149
+ }
150
+
151
+ splitByPlane (plane, coplanarFrontNodes, coplanarBackNodes, frontNodes, backNodes) {
152
+ if (this.children.length > 0) {
153
+ // the polygon has been split, so split the children by the given plane
154
+ for (let i = 0; i < this.children.length; i++) {
155
+ const node = this.children[i]
156
+ node.splitByPlane(plane, coplanarFrontNodes, coplanarBackNodes, frontNodes, backNodes)
157
+ }
158
+ } else {
159
+ if (this.polygon !== null) {
160
+ // the polygon hasn't been split, so split this node by the given plane
161
+ this._splitByPlane(plane, coplanarFrontNodes, coplanarBackNodes, frontNodes, backNodes)
162
+ }
127
163
  }
128
164
  }
129
165
 
166
+ // PRIVATE
167
+ // If the plane doesn't intersect the polygon, the 'this' object is added to one of the arrays
168
+ // If the plane does intersect the polygon, two new child nodes are created for the front and back fragments,
169
+ // and added to both arrays.
130
170
  // only to be called for nodes with no children
131
171
  _splitByPlane (splane, coplanarFrontNodes, coplanarBackNodes, frontNodes, backNodes) {
132
- const polygon = this.polygon
133
- if (polygon) {
134
- const bound = poly3.measureBoundingSphere(polygon)
135
- const sphereRadius = bound[3] + EPS // ensure radius is LARGER then polygon
136
- const sphereCenter = bound
137
- const d = vec3.dot(splane, sphereCenter) - splane[3]
138
- if (d > sphereRadius) {
139
- frontNodes.push(this)
140
- } else if (d < -sphereRadius) {
141
- backNodes.push(this)
142
- } else {
143
- const splitResult = splitPolygonByPlane(splane, polygon)
144
- switch (splitResult.type) {
145
- case 0:
146
- // coplanar front:
147
- coplanarFrontNodes.push(this)
148
- break
172
+ // perform a quick check to see the plane is outside the bounds of the polygon
173
+ const bounds = poly3.measureBoundingSphereAndCache(this.polygon)
174
+ const sphereRadius = bounds[3] + EPS // ensure radius is LARGER then polygon
175
+ const d = vec3.dot(splane, bounds) - splane[3]
176
+ if (d > sphereRadius) {
177
+ frontNodes.push(this)
178
+ return
179
+ } else if (d < -sphereRadius) {
180
+ backNodes.push(this)
181
+ return
182
+ }
149
183
 
150
- case 1:
151
- // coplanar back:
152
- coplanarBackNodes.push(this)
153
- break
184
+ // the plane may intersect the polyogn
185
+ splitPolygonByPlane(splitResult, splane, this.polygon)
186
+ switch (splitResult.type) {
187
+ case 0:
188
+ // coplanar front:
189
+ coplanarFrontNodes.push(this)
190
+ break
154
191
 
155
- case 2:
156
- // front:
157
- frontNodes.push(this)
158
- break
192
+ case 1:
193
+ // coplanar back:
194
+ coplanarBackNodes.push(this)
195
+ break
159
196
 
160
- case 3:
161
- // back:
162
- backNodes.push(this)
163
- break
197
+ case 2:
198
+ // front:
199
+ frontNodes.push(this)
200
+ break
164
201
 
165
- case 4:
166
- // spanning:
167
- if (splitResult.front) {
168
- const frontNode = this.addChild(splitResult.front)
169
- frontNodes.push(frontNode)
170
- }
171
- if (splitResult.back) {
172
- const backNode = this.addChild(splitResult.back)
173
- backNodes.push(backNode)
174
- }
175
- break
202
+ case 3:
203
+ // back:
204
+ backNodes.push(this)
205
+ break
206
+
207
+ case 4:
208
+ // spanning:
209
+ if (splitResult.front !== null) {
210
+ const frontNode = this.addChild(splitResult.front)
211
+ frontNodes.push(frontNode)
176
212
  }
177
- }
213
+ if (splitResult.back !== null) {
214
+ const backNode = this.addChild(splitResult.back)
215
+ backNodes.push(backNode)
216
+ }
217
+ break
178
218
  }
179
219
  }
180
220
 
181
- // PRIVATE methods from here:
182
221
  // add child to a node
183
222
  // this should be called whenever the polygon is split
184
223
  // a child should be created for every fragment of the split polygon
@@ -189,7 +228,9 @@ export class PolygonTreeNode {
189
228
  return newChild
190
229
  }
191
230
 
192
- invertSub () {
231
+ // PRIVATE
232
+ // See invert()
233
+ _invertSub () {
193
234
  let children = [this]
194
235
  const queue = [children]
195
236
  let i, j, l, node
@@ -197,7 +238,7 @@ export class PolygonTreeNode {
197
238
  children = queue[i]
198
239
  for (j = 0, l = children.length; j < l; j++) {
199
240
  node = children[j]
200
- if (node.polygon) {
241
+ if (node.polygon !== null) {
201
242
  node.polygon = poly3.invert(node.polygon)
202
243
  }
203
244
  if (node.children.length > 0) queue.push(node.children)
@@ -205,34 +246,40 @@ export class PolygonTreeNode {
205
246
  }
206
247
  }
207
248
 
208
- // private method
249
+ // NOTE: This verison is SLOWER
250
+ _invertSubNew () {
251
+ if (this.polygon !== null) {
252
+ this.polygon = poly3.invert(this.polygon)
253
+ }
254
+ for (let i = 0; i < this.children.length; i++) {
255
+ const node = this.children[i]
256
+ node._invertSub()
257
+ }
258
+ }
259
+
260
+ // PRIVATE
209
261
  // remove the polygon from the node, and all parent nodes above it
210
262
  // called to invalidate parents of removed nodes
211
- recursivelyInvalidatePolygon () {
263
+ _recursivelyInvalidatePolygon () {
212
264
  this.polygon = null
213
- if (this.parent) {
214
- this.parent.recursivelyInvalidatePolygon()
265
+ if (this.parent !== null) {
266
+ this.parent._recursivelyInvalidatePolygon()
215
267
  }
216
268
  }
217
269
 
218
270
  clear () {
219
- let children = [this]
220
- const queue = [children]
221
- for (let i = 0; i < queue.length; ++i) { // queue size can change in loop, don't cache length
222
- children = queue[i]
223
- const l = children.length
224
- for (let j = 0; j < l; j++) {
225
- const node = children[j]
226
- if (node.polygon) {
227
- node.polygon = null
228
- }
229
- if (node.parent) {
230
- node.parent = null
231
- }
232
- if (node.children.length > 0) queue.push(node.children)
233
- node.children = []
234
- }
271
+ // clear children
272
+ for (let i = 0; i < this.children.length; i++) {
273
+ const node = this.children[i]
274
+ node.clear()
275
+ }
276
+ this.children.length = 0
277
+ // unlink polygon
278
+ if (this.polygon !== null) {
279
+ this.polygon = null
235
280
  }
281
+ // unlink parent
282
+ this.parent = null
236
283
  }
237
284
 
238
285
  toString () {
@@ -246,7 +293,7 @@ export class PolygonTreeNode {
246
293
  for (j = 0, l = children.length; j < l; j++) { // ok to cache length
247
294
  node = children[j]
248
295
  result += `${prefix}PolygonTreeNode (${node.isRootNode()}): ${node.children.length}`
249
- if (node.polygon) {
296
+ if (node.polygon !== null) {
250
297
  result += `\n ${prefix}polygon: ${node.polygon.vertices}\n`
251
298
  } else {
252
299
  result += '\n'
@@ -7,7 +7,7 @@ import { PolygonTreeNode } from './PolygonTreeNode.js'
7
7
  // The actual tree is kept in this.rootnode
8
8
  export class Tree {
9
9
  constructor (polygons) {
10
- this.polygonTree = new PolygonTreeNode()
10
+ this.polygonTree = new PolygonTreeNode(null, null)
11
11
  this.rootnode = new Node(null)
12
12
  if (polygons) this.addPolygons(polygons)
13
13
  }
@@ -17,10 +17,9 @@ export class Tree {
17
17
  this.rootnode.invert()
18
18
  }
19
19
 
20
- // Remove all polygons in this BSP tree that are inside the other BSP tree
21
- // `tree`.
20
+ // Remove all polygons in this tree that are inside the given tree
22
21
  clipTo (tree, alsoRemoveCoplanarFront = false) {
23
- this.rootnode.clipTo(tree, alsoRemoveCoplanarFront)
22
+ this.rootnode.clipTo(tree.rootnode, alsoRemoveCoplanarFront)
24
23
  }
25
24
 
26
25
  allPolygons () {
@@ -37,6 +36,12 @@ export class Tree {
37
36
  this.rootnode.addPolygonTreeNodes(polygonTreeNodes)
38
37
  }
39
38
 
39
+ // NOTE: This version is SLOWER
40
+ addPolygonsNew (polygons) {
41
+ this.polygonTree.addPolygons(polygons)
42
+ this.rootnode.addPolygonTreeNodes(this.polygonTree.children)
43
+ }
44
+
40
45
  clear () {
41
46
  this.polygonTree.clear()
42
47
  }
@@ -3,9 +3,11 @@ import * as vec3 from '../../../maths/vec3/index.js'
3
3
  export const splitLineSegmentByPlane = (plane, p1, p2) => {
4
4
  const direction = vec3.subtract(vec3.create(), p2, p1)
5
5
  let lambda = (plane[3] - vec3.dot(plane, p1)) / vec3.dot(plane, direction)
6
- if (Number.isNaN(lambda)) lambda = 0
7
- if (lambda > 1) lambda = 1
8
- if (lambda < 0) lambda = 0
6
+
7
+ Number.isNaN(lambda) ? lambda = 0
8
+ : lambda > 1 ? lambda = 1
9
+ : lambda < 0 ? lambda = 0
10
+ : lambda
9
11
 
10
12
  vec3.scale(direction, direction, lambda)
11
13
  vec3.add(direction, p1, direction)
@@ -0,0 +1,33 @@
1
+ import { Poly3 } from '../../../geometries/types';
2
+ import { Plane } from '../../../maths/types';
3
+
4
+ enum ResType
5
+ {
6
+ coplanar_front = 0,
7
+ coplanar_back = 1,
8
+ front = 2,
9
+ back = 3,
10
+ spanning = 4,
11
+ }
12
+
13
+
14
+ interface SplitRes
15
+ {
16
+ type: ResType,
17
+ front: Poly3,
18
+ back: Poly3;
19
+ }
20
+
21
+ // Returns object:
22
+ // .type:
23
+ // 0: coplanar-front
24
+ // 1: coplanar-back
25
+ // 2: front
26
+ // 3: back
27
+ // 4: spanning
28
+ // In case the polygon is spanning, returns:
29
+ // .front: a Polygon3 of the front part
30
+ // .back: a Polygon3 of the back part
31
+ declare function splitPolygonByPlane(plane: Plane, polygon: Poly3): SplitRes;
32
+
33
+ export default splitPolygonByPlane;
@@ -7,23 +7,27 @@ import * as poly3 from '../../../geometries/poly3/index.js'
7
7
 
8
8
  import { splitLineSegmentByPlane } from './splitLineSegmentByPlane.js'
9
9
 
10
- // Returns object:
11
- // .type:
12
- // 0: coplanar-front
13
- // 1: coplanar-back
14
- // 2: front
15
- // 3: back
16
- // 4: spanning
17
- // In case the polygon is spanning, returns:
18
- // .front: a Polygon3 of the front part
19
- // .back: a Polygon3 of the back part
20
- export const splitPolygonByPlane = (splane, polygon) => {
21
- const result = {
22
- type: null,
23
- front: null,
24
- back: null
25
- }
26
- // cache in local lets (speedup):
10
+ /*
11
+ * Split the given polygon by the given plane.
12
+ *
13
+ * @@param (Object} result - object of which to update with the result
14
+ * @param {Plane} splane - plane to split across
15
+ * @param {Poly3} ploygon - polygon of which to split
16
+ * @returns none
17
+ *
18
+ * The result is updated in place to improve performance (no allocation)
19
+ * result.type:
20
+ * 0: coplanar-front
21
+ * 1: coplanar-back
22
+ * 2: front
23
+ * 3: back
24
+ * 4: spanning
25
+ *
26
+ * In case the polygon is spanning (4)
27
+ * result.front contains null or a ploygon (front part)
28
+ * result.back contains null or a polygon (back part)
29
+ */
30
+ export const splitPolygonByPlane = (result, splane, polygon) => {
27
31
  const vertices = polygon.vertices
28
32
  const numVertices = vertices.length
29
33
  const pplane = poly3.plane(polygon)
@@ -41,17 +45,20 @@ export const splitPolygonByPlane = (splane, polygon) => {
41
45
  if (t > EPS) hasFront = true
42
46
  if (t < MINEPS) hasBack = true
43
47
  }
48
+
44
49
  if ((!hasFront) && (!hasBack)) {
45
50
  // all points coplanar
46
51
  const t = vec3.dot(splane, pplane)
47
52
  result.type = (t >= 0) ? 0 : 1
48
53
  } else if (!hasBack) {
54
+ // points only front of the plane
49
55
  result.type = 2
50
56
  } else if (!hasFront) {
57
+ // points only back of the plane
51
58
  result.type = 3
52
59
  } else {
53
- // spanning
54
- result.type = 4
60
+ // points span the plane
61
+ // split the line segments by the plane
55
62
  const frontVertices = []
56
63
  const backVertices = []
57
64
  let isback = vertexIsBack[0]
@@ -61,14 +68,10 @@ export const splitPolygonByPlane = (splane, polygon) => {
61
68
  if (nextVertexIndex >= numVertices) nextVertexIndex = 0
62
69
  const nextIsBack = vertexIsBack[nextVertexIndex]
63
70
  if (isback === nextIsBack) {
64
- // line segment is on one side of the plane:
65
- if (isback) {
66
- backVertices.push(vertex)
67
- } else {
68
- frontVertices.push(vertex)
69
- }
71
+ // line segment is on one side of the plane
72
+ isback ? backVertices.push(vertex) : frontVertices.push(vertex)
70
73
  } else {
71
- // line segment intersects plane:
74
+ // line segment spans the plane
72
75
  const nextPoint = vertices[nextVertexIndex]
73
76
  const intersectionPoint = splitLineSegmentByPlane(splane, vertex, nextPoint)
74
77
  if (isback) {
@@ -82,8 +85,9 @@ export const splitPolygonByPlane = (splane, polygon) => {
82
85
  }
83
86
  }
84
87
  isback = nextIsBack
85
- } // for vertexIndex
86
- // remove duplicate vertices:
88
+ }
89
+
90
+ // remove duplicate vertices
87
91
  const EPS_SQUARED = EPS * EPS
88
92
  if (backVertices.length >= 3) {
89
93
  let prevVertex = backVertices[backVertices.length - 1]
@@ -107,12 +111,13 @@ export const splitPolygonByPlane = (splane, polygon) => {
107
111
  prevVertex = vertex
108
112
  }
109
113
  }
110
- if (frontVertices.length >= 3) {
111
- result.front = poly3.fromVerticesAndPlane(frontVertices, pplane)
112
- }
113
- if (backVertices.length >= 3) {
114
- result.back = poly3.fromVerticesAndPlane(backVertices, pplane)
115
- }
114
+
115
+ // assemble the result
116
+ result.type = 4
117
+
118
+ result.front = frontVertices.length >= 3 ? poly3.fromVerticesAndPlane(frontVertices, pplane) : null
119
+
120
+ result.back = backVertices.length >= 3 ? poly3.fromVerticesAndPlane(backVertices, pplane) : null
116
121
  }
117
122
  }
118
123
  return result
@@ -1,5 +1,5 @@
1
1
  import { areAllShapesTheSameType } from '../../utils/areAllShapesTheSameType.js'
2
- import { flatten } from '../../utils/flatten.js'
2
+ import { coalesce } from '../../utils/coalesce.js'
3
3
 
4
4
  import * as geom2 from '../../geometries/geom2/index.js'
5
5
  import * as geom3 from '../../geometries/geom3/index.js'
@@ -12,11 +12,11 @@ import { unionGeom3 } from './unionGeom3.js'
12
12
  * The given geometries should be of the same type, either geom2 or geom3.
13
13
  *
14
14
  * @param {...Object} geometries - list of geometries
15
- * @returns {Geom2|geom3} a new geometry
15
+ * @returns {Geom2|Geom3} a new geometry
16
16
  * @alias module:modeling/booleans.union
17
17
  *
18
18
  * @example
19
- * let myshape = union(cube({size: [5,5,5]}), cube({size: [5,5,5], center: [5,5,5]}))
19
+ * let myshape = union(cube({size: 5}), cube({size: 5, center: [3,3,3]}))
20
20
  *
21
21
  * @example
22
22
  * +-------+ +-------+
@@ -29,9 +29,9 @@ import { unionGeom3 } from './unionGeom3.js'
29
29
  * +-------+ +-------+
30
30
  */
31
31
  export const union = (...geometries) => {
32
- geometries = flatten(geometries)
33
- if (geometries.length === 0) throw new Error('union wrong number of arguments')
32
+ geometries = coalesce(geometries)
34
33
 
34
+ if (geometries.length === 0) return undefined
35
35
  if (!areAllShapesTheSameType(geometries)) {
36
36
  throw new Error('union arguments must be the same geometry type')
37
37
  }
@@ -4,11 +4,11 @@ import { geom2, geom3 } from '../../geometries/index.js'
4
4
 
5
5
  import { union } from './index.js'
6
6
 
7
- test('union error wrong number of arguments', (t) => {
8
- const message = 'union wrong number of arguments'
9
- t.throws(() => union(), { message })
10
- t.throws(() => union([]), { message })
11
- t.throws(() => union([[], []]), { message })
7
+ test('union empty arguments', (t) => {
8
+ t.is(union(), undefined)
9
+ t.is(union([]), undefined)
10
+ t.is(union([[], []]), undefined)
11
+ t.is(union(null, null), undefined)
12
12
  })
13
13
 
14
14
  test('union error different geometry types', (t) => {
@@ -20,6 +20,5 @@ test('union error non-geometries', (t) => {
20
20
  const message = 'union unsupported geometry type'
21
21
  t.throws(() => union([1, 2, 3], [4, 5, 6]), { message })
22
22
  t.throws(() => union([], [123]), { message })
23
- t.throws(() => union("one", "two"), { message })
24
- t.throws(() => union(null, null), { message })
23
+ t.throws(() => union('one', 'two'), { message })
25
24
  })
@@ -1,16 +1,12 @@
1
- import { flatten } from '../../utils/flatten.js'
2
-
3
1
  import { UNION } from './martinez/operation.js'
4
2
  import { boolean } from './martinez/index.js'
5
3
 
6
4
  /*
7
5
  * Return a new 2D geometry representing the total space in the given 2D geometries.
8
- * @param {...geom2} geometries - list of 2D geometries to union
6
+ * @param {Geom2[]} geometries - a flat list of 2D geometries to union
9
7
  * @returns {Geom2} new 2D geometry
10
8
  */
11
- export const unionGeom2 = (...geometries) => {
12
- geometries = flatten(geometries)
13
-
9
+ export const unionGeom2 = (geometries) => {
14
10
  let newGeometry = geometries.shift()
15
11
  geometries.forEach((geometry) => {
16
12
  newGeometry = boolean(newGeometry, geometry, UNION)