@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
|
@@ -4,11 +4,11 @@ import { geom2, geom3 } from '../../geometries/index.js'
|
|
|
4
4
|
|
|
5
5
|
import { intersect } from './index.js'
|
|
6
6
|
|
|
7
|
-
test('intersect
|
|
8
|
-
|
|
9
|
-
t.
|
|
10
|
-
t.
|
|
11
|
-
t.
|
|
7
|
+
test('intersect empty arguments', (t) => {
|
|
8
|
+
t.is(intersect(), undefined)
|
|
9
|
+
t.is(intersect([]), undefined)
|
|
10
|
+
t.is(intersect([[], []]), undefined)
|
|
11
|
+
t.is(intersect(null, null), undefined)
|
|
12
12
|
})
|
|
13
13
|
|
|
14
14
|
test('intersect error different geometry types', (t) => {
|
|
@@ -20,6 +20,5 @@ test('intersect error non-geometries', (t) => {
|
|
|
20
20
|
const message = 'intersect unsupported geometry type'
|
|
21
21
|
t.throws(() => intersect([1, 2, 3], [4, 5, 6]), { message })
|
|
22
22
|
t.throws(() => intersect([], [123]), { message })
|
|
23
|
-
t.throws(() => intersect(
|
|
24
|
-
t.throws(() => intersect(null, null), { message })
|
|
23
|
+
t.throws(() => intersect('one', 'two'), { message })
|
|
25
24
|
})
|
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
import { flatten } from '../../utils/flatten.js'
|
|
2
|
-
|
|
3
1
|
import { INTERSECTION } from './martinez/operation.js'
|
|
4
2
|
import { boolean } from './martinez/index.js'
|
|
5
3
|
|
|
6
4
|
/*
|
|
7
5
|
* Return a new 2D geometry representing space in both the first geometry and
|
|
8
6
|
* in the subsequent geometries. None of the given geometries are modified.
|
|
9
|
-
* @param {
|
|
7
|
+
* @param {Geom2[]} geometries - a flat list of 2D geometries
|
|
10
8
|
* @returns {Geom2} new 2D geometry
|
|
11
9
|
*/
|
|
12
|
-
export const intersectGeom2 = (
|
|
13
|
-
geometries = flatten(geometries)
|
|
14
|
-
|
|
10
|
+
export const intersectGeom2 = (geometries) => {
|
|
15
11
|
let newGeometry = geometries.shift()
|
|
16
12
|
geometries.forEach((geometry) => {
|
|
17
13
|
newGeometry = boolean(newGeometry, geometry, INTERSECTION)
|
|
@@ -6,7 +6,7 @@ import { geom2 } from '../../geometries/index.js'
|
|
|
6
6
|
|
|
7
7
|
import { measureArea } from '../../measurements/index.js'
|
|
8
8
|
|
|
9
|
-
import { circle, rectangle } from '../../primitives/index.js'
|
|
9
|
+
import { circle, rectangle, square } from '../../primitives/index.js'
|
|
10
10
|
|
|
11
11
|
import { intersect } from './index.js'
|
|
12
12
|
|
|
@@ -73,3 +73,27 @@ test('intersect: intersect of one or more geom2 objects produces expected geomet
|
|
|
73
73
|
t.is(obs.length, 8)
|
|
74
74
|
t.true(comparePoints(obs, exp))
|
|
75
75
|
})
|
|
76
|
+
|
|
77
|
+
test('intersect with undefined/null values', (t) => {
|
|
78
|
+
const square1 = square({ size: 8 })
|
|
79
|
+
const square2 = square({ size: 6 })
|
|
80
|
+
const square3 = square({ size: 4 })
|
|
81
|
+
const geometries = [square1, undefined, square2, null, square3]
|
|
82
|
+
|
|
83
|
+
const obs = intersect(...geometries)
|
|
84
|
+
const pts = geom2.toPoints(obs)
|
|
85
|
+
t.notThrows(() => geom2.validate(obs))
|
|
86
|
+
t.is(pts.length, 4)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('intersect of nested arrays', (t) => {
|
|
90
|
+
const square1 = square({ size: 8 })
|
|
91
|
+
const square2 = square({ size: 6 })
|
|
92
|
+
const square3 = square({ size: 4 })
|
|
93
|
+
const geometries = [square1, [square2, [square3]]]
|
|
94
|
+
|
|
95
|
+
const obs = intersect(...geometries)
|
|
96
|
+
const pts = geom2.toPoints(obs)
|
|
97
|
+
t.notThrows(() => geom2.validate(obs))
|
|
98
|
+
t.is(pts.length, 4)
|
|
99
|
+
})
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { flatten } from '../../utils/flatten.js'
|
|
2
|
-
|
|
3
1
|
import { retessellate } from '../modifiers/retessellate.js'
|
|
4
2
|
|
|
5
3
|
import { intersectGeom3Sub } from './intersectGeom3Sub.js'
|
|
@@ -7,12 +5,10 @@ import { intersectGeom3Sub } from './intersectGeom3Sub.js'
|
|
|
7
5
|
/*
|
|
8
6
|
* Return a new 3D geometry representing space in both the first geometry and
|
|
9
7
|
* in the subsequent geometries. None of the given geometries are modified.
|
|
10
|
-
* @param {
|
|
8
|
+
* @param {Geom3[]} geometries - a flat list of 3D geometries
|
|
11
9
|
* @returns {Geom3} new 3D geometry
|
|
12
10
|
*/
|
|
13
|
-
export const intersectGeom3 = (
|
|
14
|
-
geometries = flatten(geometries)
|
|
15
|
-
|
|
11
|
+
export const intersectGeom3 = (geometries) => {
|
|
16
12
|
let newGeometry = geometries.shift()
|
|
17
13
|
geometries.forEach((geometry) => {
|
|
18
14
|
newGeometry = intersectGeom3Sub(newGeometry, geometry)
|
|
@@ -4,7 +4,7 @@ import { comparePolygonsAsPoints } from '../../../test/helpers/index.js'
|
|
|
4
4
|
|
|
5
5
|
import { geom3 } from '../../geometries/index.js'
|
|
6
6
|
|
|
7
|
-
import { measureVolume } from '../../measurements/index.js'
|
|
7
|
+
import { measureArea, measureVolume } from '../../measurements/index.js'
|
|
8
8
|
|
|
9
9
|
import { sphere, cuboid } from '../../primitives/index.js'
|
|
10
10
|
|
|
@@ -69,6 +69,7 @@ test('intersect: intersect of one or more geom3 objects produces expected geomet
|
|
|
69
69
|
[[8.65956056235493e-17, 8.659560562354935e-17, 2], [1.4142135623730951, 3.4638242249419736e-16, 1.414213562373095], [0.9999999999999998, 1.0000000000000002, 1.414213562373095]]
|
|
70
70
|
]
|
|
71
71
|
t.notThrows.skip(() => geom3.validate(result1))
|
|
72
|
+
t.is(measureArea(result1), 44.053756306589825)
|
|
72
73
|
t.is(measureVolume(result1), 25.751611331979678)
|
|
73
74
|
t.is(obs.length, 32)
|
|
74
75
|
t.true(comparePolygonsAsPoints(obs, exp))
|
|
@@ -79,6 +80,7 @@ test('intersect: intersect of one or more geom3 objects produces expected geomet
|
|
|
79
80
|
const result2 = intersect(geometry1, geometry2)
|
|
80
81
|
obs = geom3.toPoints(result2)
|
|
81
82
|
t.notThrows(() => geom3.validate(result2))
|
|
83
|
+
t.is(measureArea(result2), 0)
|
|
82
84
|
t.is(measureVolume(result2), 0)
|
|
83
85
|
t.is(obs.length, 0)
|
|
84
86
|
|
|
@@ -99,6 +101,7 @@ test('intersect: intersect of one or more geom3 objects produces expected geomet
|
|
|
99
101
|
]
|
|
100
102
|
|
|
101
103
|
t.notThrows(() => geom3.validate(result3))
|
|
104
|
+
t.is(measureArea(result3), 6)
|
|
102
105
|
t.is(measureVolume(result3), 1.0000000000000009)
|
|
103
106
|
t.is(obs.length, 6)
|
|
104
107
|
t.true(comparePolygonsAsPoints(obs, exp))
|
|
@@ -107,6 +110,7 @@ test('intersect: intersect of one or more geom3 objects produces expected geomet
|
|
|
107
110
|
const result4 = intersect(geometry1, geometry3)
|
|
108
111
|
obs = geom3.toPoints(result4)
|
|
109
112
|
t.notThrows.skip(() => geom3.validate(result4))
|
|
113
|
+
t.is(measureArea(result4), 44.053756306589825)
|
|
110
114
|
t.is(measureVolume(result4), 25.751611331979678)
|
|
111
115
|
t.is(obs.length, 32)
|
|
112
116
|
})
|
|
@@ -23,23 +23,18 @@ export const compareEvents = (e1, e2) => {
|
|
|
23
23
|
// Event with lower y-coordinate is processed first
|
|
24
24
|
if (p1[1] !== p2[1]) return p1[1] > p2[1] ? 1 : -1
|
|
25
25
|
|
|
26
|
-
return specialCases(e1, e2, p1, p2)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const specialCases = (e1, e2, p1, p2) => {
|
|
30
26
|
// Same coordinates, but one is a left endpoint and the other is
|
|
31
27
|
// a right endpoint. The right endpoint is processed first
|
|
32
28
|
if (e1.left !== e2.left) { return e1.left ? 1 : -1 }
|
|
33
29
|
|
|
34
|
-
// const p2 = e1.otherEvent.point, p3 = e2.otherEvent.point
|
|
35
|
-
// const sa = (p1[0] - p3[0]) * (p2[1] - p3[1]) - (p2[0] - p3[0]) * (p1[1] - p3[1])
|
|
36
30
|
// Same coordinates, both events
|
|
37
31
|
// are left endpoints or right endpoints.
|
|
38
32
|
// not collinear
|
|
39
33
|
if (signedArea(p1, e1.otherEvent.point, e2.otherEvent.point) !== 0) {
|
|
40
|
-
// the event associate to the
|
|
34
|
+
// the event associate to the lowest segment is processed first
|
|
41
35
|
return (!e1.isBelow(e2.otherEvent.point)) ? 1 : -1
|
|
42
36
|
}
|
|
43
37
|
|
|
38
|
+
// Same coordinates, subject events are processed first
|
|
44
39
|
return (!e1.isSubject && e2.isSubject) ? 1 : -1
|
|
45
40
|
}
|
|
@@ -12,45 +12,39 @@ import { Contour } from './contour.js'
|
|
|
12
12
|
* @return {SweepEvent[]}
|
|
13
13
|
*/
|
|
14
14
|
const orderEvents = (sortedEvents) => {
|
|
15
|
-
let event, i, len, tmp
|
|
16
15
|
const resultEvents = []
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
(!event.left && event.otherEvent.inResult)) {
|
|
21
|
-
resultEvents.push(event)
|
|
16
|
+
sortedEvents.forEach((e) => {
|
|
17
|
+
if ((e.left && e.inResult) || (!e.left && e.otherEvent.inResult)) {
|
|
18
|
+
resultEvents.push(e)
|
|
22
19
|
}
|
|
23
|
-
}
|
|
20
|
+
})
|
|
24
21
|
// Due to overlapping edges the resultEvents array can be not wholly sorted
|
|
25
22
|
let sorted = false
|
|
26
23
|
while (!sorted) {
|
|
27
24
|
sorted = true
|
|
28
|
-
|
|
25
|
+
const len = resultEvents.length
|
|
26
|
+
for (let i = 0; i < len; i++) {
|
|
29
27
|
if ((i + 1) < len &&
|
|
30
28
|
compareEvents(resultEvents[i], resultEvents[i + 1]) === 1) {
|
|
31
|
-
tmp = resultEvents[i]
|
|
29
|
+
const tmp = resultEvents[i]
|
|
32
30
|
resultEvents[i] = resultEvents[i + 1]
|
|
33
31
|
resultEvents[i + 1] = tmp
|
|
34
32
|
sorted = false
|
|
35
33
|
}
|
|
36
34
|
}
|
|
37
35
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
event = resultEvents[i]
|
|
41
|
-
event.otherPos = i
|
|
42
|
-
}
|
|
36
|
+
// and index
|
|
37
|
+
resultEvents.forEach((e, i) => { e.otherPos = i })
|
|
43
38
|
|
|
44
39
|
// imagine, the right event is found in the beginning of the queue,
|
|
45
40
|
// when his left counterpart is not marked yet
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
event.otherEvent.otherPos = tmp
|
|
41
|
+
resultEvents.forEach((e) => {
|
|
42
|
+
if (!e.left) {
|
|
43
|
+
const otherPos = e.otherPos
|
|
44
|
+
e.otherPos = e.otherEvent.otherPos
|
|
45
|
+
e.otherEvent.otherPos = otherPos
|
|
52
46
|
}
|
|
53
|
-
}
|
|
47
|
+
})
|
|
54
48
|
|
|
55
49
|
return resultEvents
|
|
56
50
|
}
|
|
@@ -62,14 +56,15 @@ const orderEvents = (sortedEvents) => {
|
|
|
62
56
|
* @return {number}
|
|
63
57
|
*/
|
|
64
58
|
const nextPos = (pos, resultEvents, processed, origPos) => {
|
|
65
|
-
let newPos = pos + 1
|
|
66
|
-
const p = resultEvents[pos].point
|
|
67
|
-
let p1
|
|
68
59
|
const length = resultEvents.length
|
|
69
60
|
|
|
61
|
+
const p0 = resultEvents[pos].point
|
|
62
|
+
|
|
63
|
+
let newPos = pos + 1
|
|
64
|
+
let p1
|
|
70
65
|
if (newPos < length) { p1 = resultEvents[newPos].point }
|
|
71
66
|
|
|
72
|
-
while (newPos < length && p1[0] ===
|
|
67
|
+
while (newPos < length && p1[0] === p0[0] && p1[1] === p0[1]) {
|
|
73
68
|
if (!processed[newPos]) {
|
|
74
69
|
return newPos
|
|
75
70
|
} else {
|
|
@@ -81,7 +76,6 @@ const nextPos = (pos, resultEvents, processed, origPos) => {
|
|
|
81
76
|
}
|
|
82
77
|
|
|
83
78
|
newPos = pos - 1
|
|
84
|
-
|
|
85
79
|
while (processed[newPos] && newPos > origPos) {
|
|
86
80
|
newPos--
|
|
87
81
|
}
|
|
@@ -90,7 +84,8 @@ const nextPos = (pos, resultEvents, processed, origPos) => {
|
|
|
90
84
|
}
|
|
91
85
|
|
|
92
86
|
const initializeContourFromContext = (event, contours, contourId) => {
|
|
93
|
-
const contour = new Contour()
|
|
87
|
+
const contour = new Contour() // default is exterior contour of depth 0
|
|
88
|
+
|
|
94
89
|
if (event.prevInResult != null) {
|
|
95
90
|
const prevInResult = event.prevInResult
|
|
96
91
|
// Note that it is valid to query the "previous in result" for its output contour id,
|
|
@@ -99,6 +94,7 @@ const initializeContourFromContext = (event, contours, contourId) => {
|
|
|
99
94
|
// result".
|
|
100
95
|
const lowerContourId = prevInResult.outputContourId
|
|
101
96
|
const lowerResultTransition = prevInResult.resultTransition
|
|
97
|
+
|
|
102
98
|
if (lowerContourId < 0) {
|
|
103
99
|
contour.holeOf = null
|
|
104
100
|
contour.depth = 0
|
|
@@ -122,30 +118,24 @@ const initializeContourFromContext = (event, contours, contourId) => {
|
|
|
122
118
|
}
|
|
123
119
|
} else {
|
|
124
120
|
// We are outside => this contour is an exterior contour of same depth.
|
|
125
|
-
contour.holeOf = null
|
|
126
121
|
contour.depth = contours[lowerContourId].depth
|
|
127
122
|
}
|
|
128
|
-
} else {
|
|
129
|
-
// There is no lower/previous contour => this contour is an exterior contour of depth 0.
|
|
130
|
-
contour.holeOf = null
|
|
131
|
-
contour.depth = 0
|
|
132
123
|
}
|
|
133
124
|
return contour
|
|
134
125
|
}
|
|
135
126
|
|
|
136
127
|
/**
|
|
137
128
|
* @param {Array.<SweepEvent>} sortedEvents
|
|
138
|
-
* @return
|
|
129
|
+
* @return array of Contour
|
|
139
130
|
*/
|
|
140
131
|
export const connectEdges = (sortedEvents) => {
|
|
141
132
|
const resultEvents = orderEvents(sortedEvents)
|
|
142
|
-
const
|
|
133
|
+
const evlen = resultEvents.length
|
|
143
134
|
|
|
144
|
-
|
|
145
|
-
const processed = {}
|
|
135
|
+
const processed = []
|
|
146
136
|
const contours = []
|
|
147
137
|
|
|
148
|
-
for (let i = 0; i <
|
|
138
|
+
for (let i = 0; i < evlen; i++) {
|
|
149
139
|
if (processed[i]) {
|
|
150
140
|
continue
|
|
151
141
|
}
|
|
@@ -156,7 +146,7 @@ export const connectEdges = (sortedEvents) => {
|
|
|
156
146
|
// Helper function that combines marking an event as processed with assigning its output contour ID
|
|
157
147
|
const markAsProcessed = (pos) => {
|
|
158
148
|
processed[pos] = true
|
|
159
|
-
if (pos <
|
|
149
|
+
if (pos < evlen) {
|
|
160
150
|
resultEvents[pos].outputContourId = contourId
|
|
161
151
|
}
|
|
162
152
|
}
|
|
@@ -164,8 +154,7 @@ export const connectEdges = (sortedEvents) => {
|
|
|
164
154
|
let pos = i
|
|
165
155
|
const origPos = i
|
|
166
156
|
|
|
167
|
-
|
|
168
|
-
contour.points.push(initial)
|
|
157
|
+
contour.points.push(resultEvents[pos].point)
|
|
169
158
|
|
|
170
159
|
while (true) {
|
|
171
160
|
markAsProcessed(pos)
|
|
@@ -177,7 +166,7 @@ export const connectEdges = (sortedEvents) => {
|
|
|
177
166
|
|
|
178
167
|
pos = nextPos(pos, resultEvents, processed, origPos)
|
|
179
168
|
|
|
180
|
-
if (pos === origPos || pos >=
|
|
169
|
+
if (pos === origPos || pos >= evlen) {
|
|
181
170
|
break
|
|
182
171
|
}
|
|
183
172
|
}
|
|
@@ -2,28 +2,29 @@ import { SweepEvent } from './sweepEvent.js'
|
|
|
2
2
|
import { compareEvents } from './compareEvents.js'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
* @param {
|
|
5
|
+
* Divide the given segment at the given point, push the parts on the given queue.
|
|
6
|
+
* @param {SweepEvent} segment
|
|
7
|
+
* @param {Array.<Number>} point
|
|
7
8
|
* @param {Queue} queue
|
|
8
|
-
* @return {Queue}
|
|
9
|
+
* @return {Queue} given queue
|
|
9
10
|
*/
|
|
10
|
-
export const divideSegment = (
|
|
11
|
-
const r = new SweepEvent(
|
|
12
|
-
const l = new SweepEvent(
|
|
11
|
+
export const divideSegment = (segment, point, queue) => {
|
|
12
|
+
const r = new SweepEvent(point, false, segment, segment.isSubject)
|
|
13
|
+
const l = new SweepEvent(point, true, segment.otherEvent, segment.isSubject)
|
|
13
14
|
|
|
14
|
-
r.contourId = l.contourId =
|
|
15
|
+
r.contourId = l.contourId = segment.contourId
|
|
15
16
|
|
|
16
17
|
// avoid a rounding error. The left event would be processed after the right event
|
|
17
|
-
if (compareEvents(l,
|
|
18
|
-
|
|
18
|
+
if (compareEvents(l, segment.otherEvent) > 0) {
|
|
19
|
+
segment.otherEvent.left = true
|
|
19
20
|
l.left = false
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
// avoid a rounding error. The left event would be processed after the right event
|
|
23
24
|
// if (compareEvents(se, r) > 0) {}
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
segment.otherEvent.otherEvent = l
|
|
27
|
+
segment.otherEvent = r
|
|
27
28
|
|
|
28
29
|
queue.push(l)
|
|
29
30
|
queue.push(r)
|
|
@@ -9,26 +9,23 @@ import { DIFFERENCE } from './operation.js'
|
|
|
9
9
|
import { SweepEvent } from './sweepEvent.js'
|
|
10
10
|
import { Queue } from './tinyqueue.js'
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
const min = Math.min
|
|
12
|
+
let externalRingId = 0
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const processPolygon = (contourOrHole, isSubject, depth, queue, bbox, isExteriorRing) => {
|
|
14
|
+
const processPolygon = (contourOrHole, isSubject, ringId, queue, bbox, isExteriorRing) => {
|
|
18
15
|
const len = contourOrHole.length - 1
|
|
19
|
-
let s1, s2, e1, e2
|
|
20
16
|
for (let i = 0; i < len; i++) {
|
|
21
|
-
s1 = contourOrHole[i]
|
|
22
|
-
s2 = contourOrHole[i + 1]
|
|
23
|
-
e1 = new SweepEvent(s1, false, undefined, isSubject)
|
|
24
|
-
e2 = new SweepEvent(s2, false, e1, isSubject)
|
|
17
|
+
const s1 = contourOrHole[i]
|
|
18
|
+
const s2 = contourOrHole[i + 1]
|
|
19
|
+
const e1 = new SweepEvent(s1, false, undefined, isSubject)
|
|
20
|
+
const e2 = new SweepEvent(s2, false, e1, isSubject)
|
|
21
|
+
|
|
25
22
|
e1.otherEvent = e2
|
|
26
23
|
|
|
27
24
|
if (s1[0] === s2[0] && s1[1] === s2[1]) {
|
|
28
25
|
continue // skip collapsed edges, or it breaks
|
|
29
26
|
}
|
|
30
27
|
|
|
31
|
-
e1.contourId = e2.contourId =
|
|
28
|
+
e1.contourId = e2.contourId = ringId
|
|
32
29
|
if (!isExteriorRing) {
|
|
33
30
|
e1.isExteriorRing = false
|
|
34
31
|
e2.isExteriorRing = false
|
|
@@ -41,10 +38,10 @@ const processPolygon = (contourOrHole, isSubject, depth, queue, bbox, isExterior
|
|
|
41
38
|
|
|
42
39
|
const x = s1[0]
|
|
43
40
|
const y = s1[1]
|
|
44
|
-
bbox[0] = min(bbox[0], x)
|
|
45
|
-
bbox[1] = min(bbox[1], y)
|
|
46
|
-
bbox[2] = max(bbox[2], x)
|
|
47
|
-
bbox[3] = max(bbox[3], y)
|
|
41
|
+
bbox[0] = Math.min(bbox[0], x)
|
|
42
|
+
bbox[1] = Math.min(bbox[1], y)
|
|
43
|
+
bbox[2] = Math.max(bbox[2], x)
|
|
44
|
+
bbox[3] = Math.max(bbox[3], y)
|
|
48
45
|
|
|
49
46
|
// Pushing it so the queue is sorted from left to right,
|
|
50
47
|
// with object on the left having the highest priority.
|
|
@@ -55,24 +52,23 @@ const processPolygon = (contourOrHole, isSubject, depth, queue, bbox, isExterior
|
|
|
55
52
|
|
|
56
53
|
export const fillQueue = (subject, clipping, sbbox, cbbox, operation) => {
|
|
57
54
|
const eventQueue = new Queue([], compareEvents)
|
|
58
|
-
let polygonSet, isExteriorRing, i, ii, j, jj //, k, kk
|
|
59
55
|
|
|
60
|
-
for (i = 0
|
|
61
|
-
polygonSet = subject[i]
|
|
62
|
-
for (j = 0
|
|
63
|
-
isExteriorRing = j === 0
|
|
64
|
-
if (isExteriorRing)
|
|
65
|
-
processPolygon(polygonSet[j], true,
|
|
56
|
+
for (let i = 0; i < subject.length; i++) {
|
|
57
|
+
const polygonSet = subject[i]
|
|
58
|
+
for (let j = 0; j < polygonSet.length; j++) {
|
|
59
|
+
const isExteriorRing = j === 0
|
|
60
|
+
if (isExteriorRing) externalRingId++
|
|
61
|
+
processPolygon(polygonSet[j], true, externalRingId, eventQueue, sbbox, isExteriorRing)
|
|
66
62
|
}
|
|
67
63
|
}
|
|
68
64
|
|
|
69
|
-
for (i = 0
|
|
70
|
-
polygonSet = clipping[i]
|
|
71
|
-
for (j = 0
|
|
72
|
-
isExteriorRing = j === 0
|
|
65
|
+
for (let i = 0; i < clipping.length; i++) {
|
|
66
|
+
const polygonSet = clipping[i]
|
|
67
|
+
for (let j = 0; j < polygonSet.length; j++) {
|
|
68
|
+
let isExteriorRing = j === 0
|
|
73
69
|
if (operation === DIFFERENCE) isExteriorRing = false
|
|
74
|
-
if (isExteriorRing)
|
|
75
|
-
processPolygon(polygonSet[j], false,
|
|
70
|
+
if (isExteriorRing) externalRingId++
|
|
71
|
+
processPolygon(polygonSet[j], false, externalRingId, eventQueue, cbbox, isExteriorRing)
|
|
76
72
|
}
|
|
77
73
|
}
|
|
78
74
|
|
|
@@ -135,6 +135,7 @@ export const boolean = (subjectGeom, clippingGeom, operation) => {
|
|
|
135
135
|
// Followed by holes if any
|
|
136
136
|
for (let j = 0; j < contour.holeIds.length; j++) {
|
|
137
137
|
const holeId = contour.holeIds[j]
|
|
138
|
+
// Reverse the order of points for holes
|
|
138
139
|
const holePoints = contours[holeId].points
|
|
139
140
|
const hole = []
|
|
140
141
|
for (let k = holePoints.length - 2; k >= 0; k--) {
|
|
@@ -146,7 +147,7 @@ export const boolean = (subjectGeom, clippingGeom, operation) => {
|
|
|
146
147
|
}
|
|
147
148
|
}
|
|
148
149
|
|
|
149
|
-
if (polygons) {
|
|
150
|
+
if (polygons.length) {
|
|
150
151
|
return fromOutlines(polygons.flat())
|
|
151
152
|
} else {
|
|
152
153
|
return geom2.create()
|
|
@@ -15,36 +15,40 @@ import {
|
|
|
15
15
|
} from './edgeType.js'
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
+
* and split the segments if an intersection is detected.
|
|
18
19
|
* @param {SweepEvent} se1
|
|
19
20
|
* @param {SweepEvent} se2
|
|
20
21
|
* @param {Queue} queue
|
|
21
|
-
* @return
|
|
22
|
+
* @return
|
|
23
|
+
* 0=no intersection
|
|
24
|
+
* 1=intersect point
|
|
25
|
+
* 2=overlap, left points cooinciding
|
|
26
|
+
* 3=overlap, right points cooinciding
|
|
27
|
+
* 4=segments overlap
|
|
28
|
+
* 5=segment within segment
|
|
22
29
|
*/
|
|
23
30
|
export const possibleIntersection = (se1, se2, queue) => {
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
// if (se1.isSubject === se2.isSubject) return
|
|
31
|
+
// null = no intersection
|
|
32
|
+
// array[1] = point of intersection
|
|
33
|
+
// array[2] = line overlaps, segment of overlap, i.e. two points
|
|
28
34
|
const inter = segmentIntersection(
|
|
29
35
|
se1.point, se1.otherEvent.point,
|
|
30
36
|
se2.point, se2.otherEvent.point
|
|
31
37
|
)
|
|
32
38
|
|
|
33
39
|
const nIntersections = inter ? inter.length : 0
|
|
34
|
-
if (nIntersections === 0) return 0 // no intersection
|
|
35
40
|
|
|
36
|
-
//
|
|
41
|
+
// no intersection of segments
|
|
42
|
+
if (nIntersections === 0) return 0
|
|
43
|
+
|
|
44
|
+
// single point of intersection, check the end points
|
|
37
45
|
if ((nIntersections === 1) &&
|
|
38
46
|
(equals(se1.point, se2.point) ||
|
|
39
47
|
equals(se1.otherEvent.point, se2.otherEvent.point))) {
|
|
40
48
|
return 0
|
|
41
49
|
}
|
|
42
50
|
|
|
43
|
-
|
|
44
|
-
return 0
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// The line segments associated to se1 and se2 intersect
|
|
51
|
+
// single point of intersection, divide the segments
|
|
48
52
|
if (nIntersections === 1) {
|
|
49
53
|
// if the intersection point is not an endpoint of se1
|
|
50
54
|
if (!equals(se1.point, inter[0]) && !equals(se1.otherEvent.point, inter[0])) {
|
|
@@ -58,56 +62,63 @@ export const possibleIntersection = (se1, se2, queue) => {
|
|
|
58
62
|
return 1
|
|
59
63
|
}
|
|
60
64
|
|
|
61
|
-
//
|
|
62
|
-
|
|
65
|
+
// segments overlap, check for same subject/clipping
|
|
66
|
+
if (nIntersections === 2 && se1.isSubject === se2.isSubject) {
|
|
67
|
+
return 0
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// segments overlap, determine the overlap, and divide segments
|
|
71
|
+
// FIXME eliminate this stack
|
|
72
|
+
const segmentEvents = []
|
|
63
73
|
let leftCoincide = false
|
|
64
74
|
let rightCoincide = false
|
|
65
75
|
|
|
66
76
|
if (equals(se1.point, se2.point)) {
|
|
67
77
|
leftCoincide = true // linked
|
|
68
78
|
} else if (compareEvents(se1, se2) === 1) {
|
|
69
|
-
|
|
79
|
+
segmentEvents.push(se2, se1)
|
|
70
80
|
} else {
|
|
71
|
-
|
|
81
|
+
segmentEvents.push(se1, se2)
|
|
72
82
|
}
|
|
73
83
|
|
|
74
84
|
if (equals(se1.otherEvent.point, se2.otherEvent.point)) {
|
|
75
85
|
rightCoincide = true
|
|
76
86
|
} else if (compareEvents(se1.otherEvent, se2.otherEvent) === 1) {
|
|
77
|
-
|
|
87
|
+
segmentEvents.push(se2.otherEvent, se1.otherEvent)
|
|
78
88
|
} else {
|
|
79
|
-
|
|
89
|
+
segmentEvents.push(se1.otherEvent, se2.otherEvent)
|
|
80
90
|
}
|
|
81
91
|
|
|
82
|
-
if (
|
|
92
|
+
if (leftCoincide) {
|
|
83
93
|
// both line segments are equal or share the left endpoint
|
|
94
|
+
// FIXME: this setting of type should be done outside this function
|
|
84
95
|
se2.type = NON_CONTRIBUTING
|
|
85
96
|
se1.type = (se2.inOut === se1.inOut) ? SAME_TRANSITION : DIFFERENT_TRANSITION
|
|
86
97
|
|
|
87
|
-
if (
|
|
88
|
-
// honestly no idea, but changing
|
|
98
|
+
if (!rightCoincide) {
|
|
99
|
+
// honestly no idea, but changing segmentEvents selection from [2, 1]
|
|
89
100
|
// to [0, 1] fixes the overlapping self-intersecting polygons issue
|
|
90
|
-
divideSegment(
|
|
101
|
+
divideSegment(segmentEvents[1].otherEvent, segmentEvents[0].point, queue)
|
|
91
102
|
}
|
|
92
103
|
return 2
|
|
93
104
|
}
|
|
94
105
|
|
|
95
106
|
// the line segments share the right endpoint
|
|
96
107
|
if (rightCoincide) {
|
|
97
|
-
divideSegment(
|
|
108
|
+
divideSegment(segmentEvents[0], segmentEvents[1].point, queue)
|
|
98
109
|
return 3
|
|
99
110
|
}
|
|
100
111
|
|
|
101
112
|
// no line segment includes totally the other one
|
|
102
|
-
if (
|
|
103
|
-
divideSegment(
|
|
104
|
-
divideSegment(
|
|
105
|
-
return
|
|
113
|
+
if (segmentEvents[0] !== segmentEvents[3].otherEvent) {
|
|
114
|
+
divideSegment(segmentEvents[0], segmentEvents[1].point, queue)
|
|
115
|
+
divideSegment(segmentEvents[1], segmentEvents[2].point, queue)
|
|
116
|
+
return 4
|
|
106
117
|
}
|
|
107
118
|
|
|
108
119
|
// one line segment includes the other one
|
|
109
|
-
divideSegment(
|
|
110
|
-
divideSegment(
|
|
120
|
+
divideSegment(segmentEvents[0], segmentEvents[1].point, queue)
|
|
121
|
+
divideSegment(segmentEvents[3].otherEvent, segmentEvents[2].point, queue)
|
|
111
122
|
|
|
112
|
-
return
|
|
123
|
+
return 5
|
|
113
124
|
}
|