@jscad/modeling 3.0.1-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 +14 -0
- package/dist/jscad-modeling.es.js +2 -2
- package/dist/jscad-modeling.min.js +2 -2
- package/package.json +2 -2
- 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/index.js +41 -0
- package/src/measurements/measureBoundingSphere.js +2 -6
- 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/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.js +39 -34
- package/src/operations/extrusions/extrudeWalls.js +3 -1
- package/src/operations/hulls/hullPoints2.js +20 -28
- package/src/operations/modifiers/mergePolygons.js +2 -3
- package/src/operations/modifiers/reTesselateCoplanarPolygons.js +7 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jscad/modeling",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2-alpha.0",
|
|
4
4
|
"description": "Constructive Solid Geometry (CSG) Library for JSCAD",
|
|
5
5
|
"homepage": "https://openjscad.xyz/",
|
|
6
6
|
"repository": "https://github.com/jscad/OpenJSCAD.org",
|
|
@@ -64,5 +64,5 @@
|
|
|
64
64
|
"rollup": "^2.79.1",
|
|
65
65
|
"rollup-plugin-banner": "^0.2.1"
|
|
66
66
|
},
|
|
67
|
-
"gitHead": "
|
|
67
|
+
"gitHead": "ac80d1a7cb0a8efb21aff9193d4a8bccb6e31f1c"
|
|
68
68
|
}
|
|
@@ -14,7 +14,7 @@ import { toPoints } from './toPoints.js'
|
|
|
14
14
|
* In other words, the trailing gradient of the geometry matches the new gradient of the curve.
|
|
15
15
|
* @param {object} options - options for construction
|
|
16
16
|
* @param {Array} options.controlPoints - list of control points (2D) for the Bézier curve
|
|
17
|
-
* @param {number} [options.
|
|
17
|
+
* @param {number} [options.segments=16] - number of segments per 360 rotation
|
|
18
18
|
* @param {Path2} geometry - the path of which to append points
|
|
19
19
|
* @returns {Path2} a new path with the appended points
|
|
20
20
|
* @alias module:modeling/geometries/path2.appendBezier
|
|
@@ -15,7 +15,7 @@ export { isA } from './isA.js'
|
|
|
15
15
|
export { isConvex } from './isConvex.js'
|
|
16
16
|
export { measureArea } from './measureArea.js'
|
|
17
17
|
export { measureBoundingBox } from './measureBoundingBox.js'
|
|
18
|
-
export { measureBoundingSphere } from './measureBoundingSphere.js'
|
|
18
|
+
export { measureBoundingSphere, measureBoundingSphereAndCache } from './measureBoundingSphere.js'
|
|
19
19
|
export { measureSignedVolume } from './measureSignedVolume.js'
|
|
20
20
|
export { plane } from './plane.js'
|
|
21
21
|
export { toVertices } from './toVertices.js'
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as vec3 from '../../maths/vec3/index.js'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
+
* Measure the bounding box of the given polygon.
|
|
5
|
+
*
|
|
4
6
|
* @param {Poly3} polygon - the polygon to measure
|
|
5
7
|
* @returns {Array} an array of two vectors (3D); minimum and maximum coordinates
|
|
6
8
|
* @alias module:modeling/geometries/poly3.measureBoundingBox
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Poly3 } from './type.d.ts'
|
|
2
2
|
import type { Vec4 } from '../../maths/vec4/type.d.ts'
|
|
3
3
|
|
|
4
|
-
export function measureBoundingSphere(polygon: Poly3): Vec4
|
|
4
|
+
export function measureBoundingSphere(out: Vec4, polygon: Poly3): Vec4
|
|
5
|
+
export function measureBoundingSphereAndCache(polygon: Poly3): Vec4
|
|
@@ -4,16 +4,14 @@ const cache = new WeakMap()
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Measure the bounding sphere of the given polygon.
|
|
7
|
+
*
|
|
8
|
+
* @param {Vec4} out - receiving vector
|
|
7
9
|
* @param {Poly3} polygon - the polygon to measure
|
|
8
10
|
* @returns {Vec4} the computed bounding sphere; center vertex (3D) and radius
|
|
9
11
|
* @alias module:modeling/geometries/poly3.measureBoundingSphere
|
|
10
12
|
*/
|
|
11
|
-
export const measureBoundingSphere = (polygon) => {
|
|
12
|
-
const boundingSphere = cache.get(polygon)
|
|
13
|
-
if (boundingSphere) return boundingSphere
|
|
14
|
-
|
|
13
|
+
export const measureBoundingSphere = (out, polygon) => {
|
|
15
14
|
const vertices = polygon.vertices
|
|
16
|
-
const out = vec4.create()
|
|
17
15
|
|
|
18
16
|
if (vertices.length === 0) {
|
|
19
17
|
out[0] = 0
|
|
@@ -31,14 +29,15 @@ export const measureBoundingSphere = (polygon) => {
|
|
|
31
29
|
let maxy = minx
|
|
32
30
|
let maxz = minx
|
|
33
31
|
|
|
34
|
-
vertices.
|
|
32
|
+
for (let i = 0; i < vertices.length; i++) {
|
|
33
|
+
const v = vertices[i]
|
|
35
34
|
if (minx[0] > v[0]) minx = v
|
|
36
35
|
if (miny[1] > v[1]) miny = v
|
|
37
36
|
if (minz[2] > v[2]) minz = v
|
|
38
37
|
if (maxx[0] < v[0]) maxx = v
|
|
39
38
|
if (maxy[1] < v[1]) maxy = v
|
|
40
39
|
if (maxz[2] < v[2]) maxz = v
|
|
41
|
-
}
|
|
40
|
+
}
|
|
42
41
|
|
|
43
42
|
out[0] = (minx[0] + maxx[0]) * 0.5 // center of sphere
|
|
44
43
|
out[1] = (miny[1] + maxy[1]) * 0.5
|
|
@@ -48,7 +47,25 @@ export const measureBoundingSphere = (polygon) => {
|
|
|
48
47
|
const z = out[2] - maxz[2]
|
|
49
48
|
out[3] = Math.sqrt(x * x + y * y + z * z) // radius of sphere
|
|
50
49
|
|
|
51
|
-
|
|
50
|
+
return out
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Measure the bounding sphere of the given polygon.
|
|
55
|
+
*
|
|
56
|
+
* This version maintains a cache, retrievning previously calculated bounds if found.
|
|
57
|
+
*
|
|
58
|
+
* @param {Poly3} polygon - the polygon to measure
|
|
59
|
+
* @returns {Vec4} the computed bounding sphere; center vertex (3D) and radius
|
|
60
|
+
* @alias module:modeling/geometries/poly3.measureBoundingSphere
|
|
61
|
+
*/
|
|
62
|
+
export const measureBoundingSphereAndCache = (polygon) => {
|
|
63
|
+
const boundingSphere = cache.get(polygon)
|
|
64
|
+
if (boundingSphere) return boundingSphere
|
|
52
65
|
|
|
66
|
+
const out = [0, 0, 0, 0]
|
|
67
|
+
measureBoundingSphere(out, polygon)
|
|
68
|
+
|
|
69
|
+
cache.set(polygon, out)
|
|
53
70
|
return out
|
|
54
71
|
}
|
|
@@ -7,19 +7,22 @@ import { measureBoundingSphere, create, transform } from './index.js'
|
|
|
7
7
|
test('poly3: measureBoundingSphere() should return correct values', (t) => {
|
|
8
8
|
let ply1 = create()
|
|
9
9
|
let exp1 = [0, 0, 0, 0]
|
|
10
|
-
let ret1 =
|
|
10
|
+
let ret1 = [0, 0, 0, 0]
|
|
11
|
+
ret1 = measureBoundingSphere(ret1, ply1)
|
|
11
12
|
t.deepEqual(ret1, exp1)
|
|
12
13
|
|
|
13
14
|
// simple triangle
|
|
14
15
|
let ply2 = create([[0, 0, 0], [0, 10, 0], [0, 10, 10]])
|
|
15
16
|
let exp2 = [0, 5, 5, 7.0710678118654755]
|
|
16
|
-
let ret2 =
|
|
17
|
+
let ret2 = [0, 0, 0, 0]
|
|
18
|
+
ret2 = measureBoundingSphere(ret2, ply2)
|
|
17
19
|
t.deepEqual(ret2, exp2)
|
|
18
20
|
|
|
19
21
|
// simple square
|
|
20
22
|
let ply3 = create([[0, 0, 0], [0, 10, 0], [0, 10, 10], [0, 0, 10]])
|
|
21
23
|
let exp3 = [0, 5, 5, 7.0710678118654755]
|
|
22
|
-
let ret3 =
|
|
24
|
+
let ret3 = [0, 0, 0, 0]
|
|
25
|
+
ret3 = measureBoundingSphere(ret3, ply3)
|
|
23
26
|
t.deepEqual(ret3, exp3)
|
|
24
27
|
|
|
25
28
|
// V-shape
|
|
@@ -37,7 +40,8 @@ test('poly3: measureBoundingSphere() should return correct values', (t) => {
|
|
|
37
40
|
]
|
|
38
41
|
let ply4 = create(vertices)
|
|
39
42
|
let exp4 = [0, 4.5, 3, 4.6097722286464435]
|
|
40
|
-
let ret4 =
|
|
43
|
+
let ret4 = [0, 0, 0, 0]
|
|
44
|
+
ret4 = measureBoundingSphere(ret4, ply4)
|
|
41
45
|
t.deepEqual(ret4, exp4)
|
|
42
46
|
|
|
43
47
|
// rotated to various angles
|
|
@@ -46,10 +50,10 @@ test('poly3: measureBoundingSphere() should return correct values', (t) => {
|
|
|
46
50
|
ply2 = transform(rotation, ply2)
|
|
47
51
|
ply3 = transform(rotation, ply3)
|
|
48
52
|
ply4 = transform(rotation, ply4)
|
|
49
|
-
ret1 = measureBoundingSphere(ply1)
|
|
50
|
-
ret2 = measureBoundingSphere(ply2)
|
|
51
|
-
ret3 = measureBoundingSphere(ply3)
|
|
52
|
-
ret4 = measureBoundingSphere(ply4)
|
|
53
|
+
ret1 = measureBoundingSphere(ret1, ply1)
|
|
54
|
+
ret2 = measureBoundingSphere(ret2, ply2)
|
|
55
|
+
ret3 = measureBoundingSphere(ret3, ply3)
|
|
56
|
+
ret4 = measureBoundingSphere(ret4, ply4)
|
|
53
57
|
exp1 = [0, 0, 0, 0]
|
|
54
58
|
t.deepEqual(ret1, exp1)
|
|
55
59
|
exp2 = [-3.5355339059327373, 3.5355339059327378, 5, 7.0710678118654755]
|
package/src/index.js
CHANGED
|
@@ -13,3 +13,44 @@ export * from './operations/hulls/index.js'
|
|
|
13
13
|
export * from './operations/modifiers/index.js'
|
|
14
14
|
export * from './operations/offsets/index.js'
|
|
15
15
|
export * from './operations/transforms/index.js'
|
|
16
|
+
|
|
17
|
+
// V2 API compatibility:
|
|
18
|
+
export * as colors from './colors/index.js'
|
|
19
|
+
export * as curves from './curves/index.js'
|
|
20
|
+
import { geom2, geom3, path2, poly2, poly3 } from './geometries/index.js'
|
|
21
|
+
export const geometries = {
|
|
22
|
+
geom2: {
|
|
23
|
+
...geom2,
|
|
24
|
+
create: (sides) => geom2.fromSides(sides),
|
|
25
|
+
fromPoints: (points) => geometries.geom2.create([points])
|
|
26
|
+
},
|
|
27
|
+
geom3,
|
|
28
|
+
path2,
|
|
29
|
+
poly2,
|
|
30
|
+
poly3: {
|
|
31
|
+
...poly3,
|
|
32
|
+
fromPoints: (points) => poly3.create([points]),
|
|
33
|
+
fromPointsAndPlane: poly3.fromVerticesAndPlane,
|
|
34
|
+
toPoints: poly3.toVertices
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export * as maths from './maths/index.js'
|
|
38
|
+
export * as measurements from './measurements/index.js'
|
|
39
|
+
export * as primitives from './primitives/index.js'
|
|
40
|
+
export * as text from './text/index.js'
|
|
41
|
+
export * as booleans from './operations/booleans/index.js'
|
|
42
|
+
import * as extrusion from './operations/extrusions/index.js'
|
|
43
|
+
import * as offsets from './operations/offsets/index.js'
|
|
44
|
+
export const extrusions = {
|
|
45
|
+
...extrusion,
|
|
46
|
+
extrudeRectangular: (opt, geom) => extrusions.extrudeLinear(opt, offsets.offset(opt, geom)),
|
|
47
|
+
slice: geometries.slice
|
|
48
|
+
}
|
|
49
|
+
export * as hulls from './operations/hulls/index.js'
|
|
50
|
+
export * as modifiers from './operations/modifiers/index.js'
|
|
51
|
+
export const expansions = {
|
|
52
|
+
...offsets,
|
|
53
|
+
expand: offsets.offset
|
|
54
|
+
}
|
|
55
|
+
export * as transforms from './operations/transforms/index.js'
|
|
56
|
+
export * as utils from './utils/index.js'
|
|
@@ -64,17 +64,13 @@ const measureBoundingSphereOfPoints = (points) => {
|
|
|
64
64
|
* Measure the bounding sphere of the given (path2) geometry.
|
|
65
65
|
* @return {[[x, y, z], radius]} the bounding sphere for the geometry
|
|
66
66
|
*/
|
|
67
|
-
const measureBoundingSphereOfPath2 = (
|
|
68
|
-
return measureBoundingSphereOfPoints(path2.toPoints(points))
|
|
69
|
-
}
|
|
67
|
+
const measureBoundingSphereOfPath2 = (geometry) => measureBoundingSphereOfPoints(path2.toPoints(geometry))
|
|
70
68
|
|
|
71
69
|
/*
|
|
72
70
|
* Measure the bounding sphere of the given (geom2) geometry.
|
|
73
71
|
* @return {[[x, y, z], radius]} the bounding sphere for the geometry
|
|
74
72
|
*/
|
|
75
|
-
const measureBoundingSphereOfGeom2 = (geometry) =>
|
|
76
|
-
return measureBoundingSphereOfPoints(geom2.toPoints(geometry))
|
|
77
|
-
}
|
|
73
|
+
const measureBoundingSphereOfGeom2 = (geometry) => measureBoundingSphereOfPoints(geom2.toPoints(geometry))
|
|
78
74
|
|
|
79
75
|
/*
|
|
80
76
|
* Measure the bounding sphere of the given (geom3) geometry.
|
|
@@ -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()
|