@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.
- package/CHANGELOG.md +49 -0
- package/LICENSE +1 -1
- package/dist/jscad-modeling.es.js +2 -2
- package/dist/jscad-modeling.min.js +2 -2
- package/package.json +2 -2
- package/rollup.config.js +1 -1
- package/src/colors/colorize.js +1 -5
- package/src/colors/colorize.test.js +8 -8
- package/src/geometries/geom2/transform.js +9 -1
- package/src/geometries/geom2/transform.test.js +57 -0
- package/src/geometries/geom3/fromPointsConvex.d.ts +4 -0
- package/src/geometries/geom3/fromPointsConvex.js +25 -0
- package/src/geometries/geom3/fromPointsConvex.test.js +32 -0
- package/src/geometries/geom3/index.d.ts +1 -0
- package/src/geometries/geom3/index.js +1 -0
- package/src/geometries/index.js +3 -4
- package/src/geometries/path2/appendBezier.js +1 -1
- package/src/geometries/poly3/index.js +1 -1
- package/src/geometries/poly3/measureBoundingBox.js +2 -0
- package/src/geometries/poly3/measureBoundingSphere.d.ts +2 -1
- package/src/geometries/poly3/measureBoundingSphere.js +25 -8
- package/src/geometries/poly3/measureBoundingSphere.test.js +12 -8
- package/src/geometries/poly3/type.d.ts +1 -1
- package/src/geometries/slice/validate.js +1 -2
- package/src/index.js +41 -0
- package/src/maths/index.js +1 -0
- package/src/maths/mat4/isOnlyTransformScale.js +1 -1
- package/src/measurements/measureAggregateArea.js +0 -1
- package/src/measurements/measureAggregateBoundingBox.js +0 -1
- package/src/measurements/measureAggregateEpsilon.js +0 -1
- package/src/measurements/measureAggregateVolume.js +0 -1
- package/src/measurements/measureArea.js +0 -1
- package/src/measurements/measureBoundingBox.js +0 -1
- package/src/measurements/measureBoundingSphere.js +2 -6
- package/src/measurements/measureEpsilon.js +0 -1
- package/src/measurements/measureVolume.js +0 -1
- package/src/operations/booleans/index.d.ts +1 -0
- package/src/operations/booleans/intersect.js +5 -5
- package/src/operations/booleans/intersect.test.js +6 -7
- package/src/operations/booleans/intersectGeom2.js +2 -6
- package/src/operations/booleans/intersectGeom2.test.js +25 -1
- package/src/operations/booleans/intersectGeom3.js +2 -6
- package/src/operations/booleans/intersectGeom3.test.js +5 -1
- package/src/operations/booleans/martinez/compareEvents.js +2 -7
- package/src/operations/booleans/martinez/connectEdges.js +30 -41
- package/src/operations/booleans/martinez/contour.js +1 -1
- package/src/operations/booleans/martinez/divideSegment.js +12 -11
- package/src/operations/booleans/martinez/fillQueue.js +24 -28
- package/src/operations/booleans/martinez/index.js +2 -1
- package/src/operations/booleans/martinez/possibleIntersection.js +41 -30
- package/src/operations/booleans/martinez/segmentIntersection.js +7 -9
- package/src/operations/booleans/martinez/splaytree.js +59 -457
- package/src/operations/booleans/martinez/subdivideSegments.js +4 -4
- package/src/operations/booleans/martinez/sweepEvent.js +3 -17
- package/src/operations/booleans/mayOverlap.js +0 -1
- package/src/operations/booleans/scission.d.ts +5 -0
- package/src/operations/booleans/scission.js +3 -5
- package/src/operations/booleans/scission.test.js +6 -0
- package/src/operations/booleans/subtract.js +5 -5
- package/src/operations/booleans/subtract.test.js +6 -7
- package/src/operations/booleans/subtractGeom2.js +2 -6
- package/src/operations/booleans/subtractGeom2.test.js +25 -1
- package/src/operations/booleans/subtractGeom3.js +2 -6
- package/src/operations/booleans/subtractGeom3.test.js +5 -1
- package/src/operations/booleans/trees/Node.js +25 -27
- package/src/operations/booleans/trees/PolygonTreeNode.js +153 -106
- package/src/operations/booleans/trees/Tree.js +9 -4
- package/src/operations/booleans/trees/splitLineSegmentByPlane.js +5 -3
- package/src/operations/booleans/trees/splitPolygonByPlane.d.ts +33 -0
- package/src/operations/booleans/trees/splitPolygonByPlane.js +39 -34
- package/src/operations/booleans/union.js +5 -5
- package/src/operations/booleans/union.test.js +6 -7
- package/src/operations/booleans/unionGeom2.js +2 -6
- package/src/operations/booleans/unionGeom2.test.js +25 -1
- package/src/operations/booleans/unionGeom3.js +2 -6
- package/src/operations/booleans/unionGeom3.test.js +6 -1
- package/src/operations/extrusions/extrudeFromSlices.test.js +8 -1
- package/src/operations/extrusions/extrudeHelical.js +2 -8
- package/src/operations/extrusions/extrudeLinear.js +1 -5
- package/src/operations/extrusions/extrudeLinear.test.js +7 -1
- package/src/operations/extrusions/extrudeRotate.js +3 -2
- package/src/operations/extrusions/extrudeRotate.test.js +13 -1
- package/src/operations/extrusions/extrudeWalls.js +3 -1
- package/src/operations/extrusions/project.js +1 -5
- package/src/operations/hulls/hull.js +6 -5
- package/src/operations/hulls/hull.test.js +56 -3
- package/src/operations/hulls/hullChain.js +11 -6
- package/src/operations/hulls/hullChain.test.js +12 -2
- package/src/operations/hulls/hullGeom2.js +5 -6
- package/src/operations/hulls/hullGeom3.js +9 -18
- package/src/operations/hulls/hullPath2.js +6 -7
- package/src/operations/hulls/hullPath2.test.js +1 -1
- package/src/operations/hulls/hullPoints2.d.ts +3 -0
- package/src/operations/hulls/hullPoints2.js +24 -30
- package/src/operations/hulls/hullPoints3.d.ts +4 -0
- package/src/operations/hulls/hullPoints3.js +21 -0
- package/src/operations/hulls/index.d.ts +2 -0
- package/src/operations/hulls/index.js +3 -1
- package/src/operations/modifiers/generalize.js +2 -6
- package/src/operations/modifiers/index.js +1 -1
- package/src/operations/modifiers/mergePolygons.js +2 -3
- package/src/operations/modifiers/reTesselateCoplanarPolygons.js +7 -7
- package/src/operations/modifiers/snap.js +2 -6
- package/src/operations/offsets/offset.js +1 -5
- package/src/operations/offsets/offsetFromPoints.test.js +0 -1
- package/src/operations/offsets/offsetGeom2.test.js +1 -0
- package/src/operations/offsets/offsetGeom3.js +0 -2
- package/src/operations/offsets/offsetGeom3.test.js +9 -1
- package/src/operations/offsets/offsetPath2.js +3 -3
- package/src/operations/transforms/align.js +8 -7
- package/src/operations/transforms/align.test.js +2 -2
- package/src/operations/transforms/center.js +6 -9
- package/src/operations/transforms/center.test.js +19 -1
- package/src/operations/transforms/mirror.js +5 -8
- package/src/operations/transforms/mirror.test.js +7 -7
- package/src/operations/transforms/rotate.js +5 -8
- package/src/operations/transforms/scale.js +5 -8
- package/src/operations/transforms/transform.js +2 -5
- package/src/operations/transforms/translate.js +5 -8
- package/src/primitives/arc.js +2 -0
- package/src/primitives/arc.test.js +11 -11
- package/src/primitives/circle.test.js +18 -8
- package/src/primitives/cube.test.js +10 -0
- package/src/primitives/cuboid.test.js +10 -0
- package/src/primitives/cylinder.test.js +12 -0
- package/src/primitives/cylinderElliptic.test.js +21 -1
- package/src/primitives/ellipse.test.js +18 -8
- package/src/primitives/ellipsoid.test.js +12 -0
- package/src/primitives/geodesicSphere.test.js +8 -0
- package/src/primitives/line.test.js +1 -1
- package/src/primitives/polygon.d.ts +1 -0
- package/src/primitives/polygon.js +13 -4
- package/src/primitives/polygon.test.js +15 -0
- package/src/primitives/polyhedron.js +1 -0
- package/src/primitives/polyhedron.test.js +8 -2
- package/src/primitives/rectangle.test.js +9 -3
- package/src/primitives/roundedCuboid.js +1 -1
- package/src/primitives/roundedCuboid.test.js +20 -4
- package/src/primitives/roundedCylinder.js +1 -1
- package/src/primitives/roundedCylinder.test.js +20 -0
- package/src/primitives/roundedRectangle.js +1 -1
- package/src/primitives/roundedRectangle.test.js +15 -6
- package/src/primitives/sphere.test.js +12 -0
- package/src/primitives/square.test.js +10 -4
- package/src/primitives/star.test.js +14 -6
- package/src/primitives/torus.js +1 -1
- package/src/primitives/torus.test.js +11 -1
- package/src/primitives/triangle.test.js +17 -9
- package/src/utils/coalesce.d.ts +3 -0
- package/src/utils/coalesce.js +20 -0
- package/src/utils/index.js +2 -2
- package/src/maths/mat4/leftMultiplyVec2.d.ts +0 -4
- package/src/maths/mat4/leftMultiplyVec2.js +0 -26
- package/src/maths/mat4/leftMultiplyVec3.d.ts +0 -4
- package/src/maths/mat4/leftMultiplyVec3.js +0 -27
- package/src/maths/mat4/mirror.d.ts +0 -4
- package/src/maths/mat4/mirror.js +0 -32
- package/src/maths/mat4/rightMultiplyVec2.d.ts +0 -4
- package/src/maths/mat4/rightMultiplyVec2.js +0 -27
- package/src/maths/mat4/rightMultiplyVec3.d.ts +0 -4
- 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
|
|
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
|
|
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.
|
|
30
|
+
this.children = []
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
// fill the
|
|
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
|
-
|
|
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
|
-
|
|
49
|
-
this.removed = true
|
|
50
|
-
this.polygon = null
|
|
48
|
+
this.polygon = null
|
|
51
49
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
56
|
+
// invalidate the parent's polygon, and of all parents above it:
|
|
57
|
+
this.parent._recursivelyInvalidatePolygon()
|
|
61
58
|
}
|
|
62
59
|
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
71
|
+
// invert all polygons in the node. Call on the root node
|
|
72
72
|
invert () {
|
|
73
|
-
if (!this.isRootNode()) throw new Error('
|
|
74
|
-
this.
|
|
73
|
+
if (!this.isRootNode()) throw new Error('PolyTreeNode03')
|
|
74
|
+
this._invertSub()
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
getPolygon () {
|
|
78
|
-
if (
|
|
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
|
|
95
|
-
if (node.children.length > 0)
|
|
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
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
121
|
-
|
|
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.
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
192
|
+
case 1:
|
|
193
|
+
// coplanar back:
|
|
194
|
+
coplanarBackNodes.push(this)
|
|
195
|
+
break
|
|
159
196
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
197
|
+
case 2:
|
|
198
|
+
// front:
|
|
199
|
+
frontNodes.push(this)
|
|
200
|
+
break
|
|
164
201
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
263
|
+
_recursivelyInvalidatePolygon () {
|
|
212
264
|
this.polygon = null
|
|
213
|
-
if (this.parent) {
|
|
214
|
-
this.parent.
|
|
265
|
+
if (this.parent !== null) {
|
|
266
|
+
this.parent._recursivelyInvalidatePolygon()
|
|
215
267
|
}
|
|
216
268
|
}
|
|
217
269
|
|
|
218
270
|
clear () {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
//
|
|
54
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
}
|
|
86
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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 {
|
|
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|
|
|
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:
|
|
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 =
|
|
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
|
|
8
|
-
|
|
9
|
-
t.
|
|
10
|
-
t.
|
|
11
|
-
t.
|
|
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(
|
|
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 {
|
|
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 = (
|
|
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)
|