@jscad/modeling 2.7.2 → 2.9.1
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 +48 -0
- package/dist/jscad-modeling.min.js +443 -398
- package/package.json +2 -2
- package/src/curves/bezier/tangentAt.test.js +1 -1
- package/src/curves/bezier/valueAt.test.js +1 -1
- package/src/geometries/geom2/index.d.ts +1 -0
- package/src/geometries/geom2/index.js +12 -1
- package/src/geometries/geom2/isA.js +2 -2
- package/src/geometries/geom2/toCompactBinary.js +4 -4
- package/src/geometries/geom2/toString.js +1 -1
- package/src/geometries/geom2/transform.test.js +1 -1
- package/src/geometries/geom2/validate.d.ts +3 -0
- package/src/geometries/geom2/validate.js +36 -0
- package/src/geometries/geom3/fromCompactBinary.js +1 -1
- package/src/geometries/geom3/index.d.ts +1 -0
- package/src/geometries/geom3/index.js +19 -1
- package/src/geometries/geom3/isA.js +2 -2
- package/src/geometries/geom3/toCompactBinary.js +4 -4
- package/src/geometries/geom3/toString.js +1 -1
- package/src/geometries/geom3/transform.test.js +1 -1
- package/src/geometries/geom3/validate.d.ts +3 -0
- package/src/geometries/geom3/validate.js +62 -0
- package/src/geometries/index.js +8 -1
- package/src/geometries/path2/eachPoint.js +3 -3
- package/src/geometries/path2/index.d.ts +1 -0
- package/src/geometries/path2/index.js +13 -1
- package/src/geometries/path2/isA.js +2 -2
- package/src/geometries/path2/reverse.js +4 -4
- package/src/geometries/path2/toCompactBinary.js +6 -6
- package/src/geometries/path2/toString.js +1 -1
- package/src/geometries/path2/transform.test.js +1 -1
- package/src/geometries/path2/validate.d.ts +3 -0
- package/src/geometries/path2/validate.js +41 -0
- package/src/geometries/poly2/arePointsInside.js +0 -35
- package/src/geometries/poly2/arePointsInside.test.js +1 -1
- package/src/geometries/poly2/index.js +6 -0
- package/src/geometries/poly3/index.d.ts +1 -0
- package/src/geometries/poly3/index.js +9 -2
- package/src/geometries/poly3/invert.js +7 -1
- package/src/geometries/poly3/isA.js +2 -2
- package/src/geometries/poly3/isConvex.js +2 -2
- package/src/geometries/poly3/measureArea.js +4 -4
- package/src/geometries/poly3/measureArea.test.js +16 -16
- package/src/geometries/poly3/measureBoundingBox.js +2 -2
- package/src/geometries/poly3/measureBoundingSphere.js +2 -2
- package/src/geometries/poly3/measureBoundingSphere.test.js +8 -8
- package/src/geometries/poly3/measureSignedVolume.js +4 -4
- package/src/geometries/poly3/toPoints.js +2 -2
- package/src/geometries/poly3/toString.js +2 -2
- package/src/geometries/poly3/transform.js +2 -2
- package/src/geometries/poly3/validate.d.ts +4 -0
- package/src/geometries/poly3/validate.js +50 -0
- package/src/maths/index.js +1 -1
- package/src/maths/line2/equals.js +2 -2
- package/src/maths/line2/fromValues.js +2 -2
- package/src/maths/line2/intersectPointOfLines.js +1 -1
- package/src/maths/line2/intersectPointOfLines.test.js +1 -1
- package/src/maths/line2/reverse.test.js +1 -1
- package/src/maths/line2/transform.test.js +1 -1
- package/src/maths/line3/equals.js +2 -2
- package/src/maths/line3/reverse.test.js +1 -1
- package/src/maths/line3/transform.test.js +1 -1
- package/src/maths/mat4/fromVectorRotation.js +1 -1
- package/src/maths/mat4/fromVectorRotation.test.js +1 -1
- package/src/maths/mat4/identity.test.js +1 -1
- package/src/maths/mat4/invert.js +18 -18
- package/src/maths/mat4/isIdentity.js +1 -1
- package/src/maths/mat4/isMirroring.js +4 -4
- package/src/maths/mat4/isMirroring.test.js +1 -1
- package/src/maths/mat4/leftMultiplyVec3.js +2 -2
- package/src/maths/mat4/toString.js +2 -2
- package/src/maths/mat4/translate.test.js +1 -1
- package/src/maths/plane/flip.test.js +1 -1
- package/src/maths/plane/fromPoints.d.ts +1 -1
- package/src/maths/plane/fromPoints.js +1 -3
- package/src/maths/plane/signedDistanceToPoint.js +1 -1
- package/src/maths/plane/transform.test.js +1 -1
- package/src/maths/utils/aboutEqualNormals.js +2 -2
- package/src/maths/vec2/abs.d.ts +1 -1
- package/src/maths/vec2/add.test.js +1 -1
- package/src/maths/vec2/angleDegrees.d.ts +1 -1
- package/src/maths/vec2/angleRadians.d.ts +1 -1
- package/src/maths/vec2/create.js +1 -1
- package/src/maths/vec2/cross.test.js +1 -1
- package/src/maths/vec2/divide.test.js +1 -1
- package/src/maths/vec2/fromAngleDegrees.js +1 -1
- package/src/maths/vec2/fromScalar.js +1 -1
- package/src/maths/vec2/length.d.ts +1 -1
- package/src/maths/vec2/length.js +1 -1
- package/src/maths/vec2/lerp.test.js +1 -1
- package/src/maths/vec2/multiply.test.js +1 -1
- package/src/maths/vec2/negate.test.js +1 -1
- package/src/maths/vec2/normal.js +1 -1
- package/src/maths/vec2/normalize.d.ts +1 -1
- package/src/maths/vec2/normalize.test.js +1 -1
- package/src/maths/vec2/rotate.test.js +1 -1
- package/src/maths/vec2/squaredLength.d.ts +1 -1
- package/src/maths/vec2/squaredLength.js +3 -3
- package/src/maths/vec2/subtract.test.js +1 -1
- package/src/maths/vec2/toString.js +1 -1
- package/src/maths/vec2/transform.test.js +1 -1
- package/src/maths/vec3/abs.d.ts +1 -1
- package/src/maths/vec3/add.test.js +1 -1
- package/src/maths/vec3/cross.test.js +1 -1
- package/src/maths/vec3/divide.test.js +1 -1
- package/src/maths/vec3/fromScalar.js +1 -1
- package/src/maths/vec3/fromVec2.d.ts +1 -1
- package/src/maths/vec3/fromVec2.js +3 -3
- package/src/maths/vec3/length.d.ts +1 -1
- package/src/maths/vec3/length.js +4 -4
- package/src/maths/vec3/lerp.test.js +1 -1
- package/src/maths/vec3/multiply.test.js +1 -1
- package/src/maths/vec3/negate.d.ts +1 -1
- package/src/maths/vec3/negate.test.js +1 -1
- package/src/maths/vec3/normalize.d.ts +1 -1
- package/src/maths/vec3/normalize.test.js +1 -1
- package/src/maths/vec3/rotateX.test.js +1 -1
- package/src/maths/vec3/rotateY.test.js +1 -1
- package/src/maths/vec3/rotateZ.test.js +1 -1
- package/src/maths/vec3/scale.test.js +1 -1
- package/src/maths/vec3/squaredLength.d.ts +1 -1
- package/src/maths/vec3/squaredLength.js +4 -4
- package/src/maths/vec3/subtract.test.js +1 -1
- package/src/maths/vec3/toString.js +1 -1
- package/src/maths/vec3/transform.test.js +1 -1
- package/src/maths/vec4/toString.js +1 -1
- package/src/maths/vec4/transform.test.js +1 -1
- package/src/measurements/measureBoundingSphere.js +4 -4
- package/src/measurements/measureCenterOfMass.js +1 -1
- package/src/measurements/measureCenterOfMass.test.js +2 -2
- package/src/operations/booleans/intersect.test.js +8 -0
- package/src/operations/booleans/mayOverlap.js +3 -3
- package/src/operations/booleans/retessellate.js +2 -2
- package/src/operations/booleans/scission.js +1 -1
- package/src/operations/booleans/scission.test.js +4 -4
- package/src/operations/booleans/subtract.js +1 -1
- package/src/operations/booleans/subtract.test.js +8 -0
- package/src/operations/booleans/trees/Node.js +10 -16
- package/src/operations/booleans/trees/PolygonTreeNode.js +13 -14
- package/src/operations/booleans/trees/Tree.js +1 -2
- package/src/operations/booleans/trees/splitPolygonByPlane.js +2 -3
- package/src/operations/booleans/union.test.js +28 -1
- package/src/operations/booleans/unionGeom3Sub.js +1 -1
- package/src/operations/expansions/expand.js +2 -2
- package/src/operations/expansions/expand.test.js +32 -55
- package/src/operations/expansions/expandShell.js +24 -18
- package/src/operations/expansions/offset.js +1 -1
- package/src/operations/expansions/offset.test.js +50 -89
- package/src/operations/expansions/offsetFromPoints.js +11 -6
- package/src/operations/extrusions/earcut/assignHoles.js +91 -0
- package/src/operations/extrusions/earcut/assignHoles.test.js +74 -0
- package/src/operations/extrusions/earcut/eliminateHoles.js +131 -0
- package/src/operations/extrusions/earcut/index.js +252 -0
- package/src/operations/extrusions/earcut/linkedList.js +58 -0
- package/src/operations/extrusions/earcut/linkedListSort.js +54 -0
- package/src/operations/extrusions/earcut/linkedPolygon.js +197 -0
- package/src/operations/extrusions/earcut/polygonHierarchy.js +64 -0
- package/src/operations/extrusions/earcut/triangle.js +16 -0
- package/src/operations/extrusions/extrudeFromSlices.js +10 -3
- package/src/operations/extrusions/extrudeFromSlices.test.js +47 -31
- package/src/operations/extrusions/extrudeLinear.js +10 -5
- package/src/operations/extrusions/extrudeLinear.test.js +91 -35
- package/src/operations/extrusions/extrudeLinearGeom2.js +5 -2
- package/src/operations/extrusions/extrudeLinearPath2.js +24 -0
- package/src/operations/extrusions/extrudeRectangular.js +1 -1
- package/src/operations/extrusions/extrudeRectangular.test.js +22 -15
- package/src/operations/extrusions/extrudeRotate.test.js +31 -27
- package/src/operations/extrusions/project.js +1 -1
- package/src/operations/extrusions/project.test.js +5 -5
- package/src/operations/extrusions/slice/calculatePlane.js +7 -4
- package/src/operations/extrusions/slice/isA.js +2 -2
- package/src/operations/extrusions/slice/repairSlice.js +47 -0
- package/src/operations/extrusions/slice/toPolygons.js +24 -60
- package/src/operations/hulls/hull.test.js +25 -2
- package/src/operations/hulls/hullChain.js +1 -1
- package/src/operations/hulls/hullChain.test.js +6 -4
- package/src/operations/hulls/hullGeom2.js +1 -1
- package/src/operations/hulls/hullPath2.js +6 -4
- package/src/operations/hulls/hullPath2.test.js +16 -0
- package/src/operations/hulls/hullPoints2.test.js +1 -1
- package/src/operations/modifiers/edges.js +1 -1
- package/src/operations/modifiers/generalize.js +1 -1
- package/src/operations/modifiers/generalize.test.js +6 -0
- package/src/operations/modifiers/snap.test.js +3 -3
- package/src/operations/transforms/align.d.ts +1 -1
- package/src/operations/transforms/align.test.js +12 -0
- package/src/operations/transforms/center.js +17 -17
- package/src/operations/transforms/center.test.js +12 -0
- package/src/operations/transforms/mirror.js +12 -12
- package/src/operations/transforms/mirror.test.js +16 -0
- package/src/operations/transforms/rotate.js +12 -12
- package/src/operations/transforms/rotate.test.js +10 -0
- package/src/operations/transforms/scale.js +19 -19
- package/src/operations/transforms/scale.test.js +15 -0
- package/src/operations/transforms/transform.js +3 -3
- package/src/operations/transforms/transform.test.js +5 -0
- package/src/operations/transforms/translate.js +14 -14
- package/src/operations/transforms/translate.test.js +16 -0
- package/src/primitives/arc.js +1 -1
- package/src/primitives/arc.test.js +11 -0
- package/src/primitives/circle.test.js +15 -9
- package/src/primitives/cube.test.js +3 -0
- package/src/primitives/cuboid.test.js +9 -24
- package/src/primitives/cylinder.test.js +7 -4
- package/src/primitives/cylinderElliptic.js +13 -6
- package/src/primitives/cylinderElliptic.test.js +72 -52
- package/src/primitives/ellipse.js +3 -1
- package/src/primitives/ellipse.test.js +14 -8
- package/src/primitives/ellipsoid.js +7 -5
- package/src/primitives/ellipsoid.test.js +84 -82
- package/src/primitives/geodesicSphere.d.ts +0 -1
- package/src/primitives/geodesicSphere.test.js +3 -0
- package/src/primitives/line.test.js +1 -0
- package/src/primitives/polygon.test.js +15 -10
- package/src/primitives/polyhedron.js +1 -1
- package/src/primitives/polyhedron.test.js +14 -42
- package/src/primitives/rectangle.test.js +3 -0
- package/src/primitives/roundedCuboid.test.js +5 -0
- package/src/primitives/roundedCylinder.js +6 -4
- package/src/primitives/roundedCylinder.test.js +40 -36
- package/src/primitives/roundedRectangle.test.js +5 -0
- package/src/primitives/sphere.test.js +52 -73
- package/src/primitives/square.test.js +3 -0
- package/src/primitives/star.test.js +6 -0
- package/src/primitives/torus.d.ts +0 -1
- package/src/primitives/torus.test.js +8 -1
- package/src/primitives/triangle.js +1 -1
- package/src/primitives/triangle.test.js +7 -0
- package/src/text/vectorText.js +2 -2
- package/src/utils/areAllShapesTheSameType.js +2 -2
- package/src/utils/areAllShapesTheSameType.test.js +17 -0
- package/src/utils/index.d.ts +1 -0
- package/src/utils/index.js +3 -1
- package/src/utils/padArrayToLength.js +1 -1
- package/src/utils/trigonometry.d.ts +2 -0
- package/src/utils/trigonometry.js +35 -0
- package/src/utils/trigonometry.test.js +25 -0
- package/test/helpers/nearlyEqual.js +4 -1
|
@@ -12,6 +12,7 @@ test('offset: offsetting a straight line produces expected geometry', (t) => {
|
|
|
12
12
|
// offset it by 2.
|
|
13
13
|
let offsetLinePath2 = offset({ delta: 2, corners: 'edge', segments: 8 }, linePath2)
|
|
14
14
|
let offsetPoints = path2.toPoints(offsetLinePath2)
|
|
15
|
+
t.notThrows(() => path2.validate(offsetLinePath2))
|
|
15
16
|
t.is(offsetPoints.length, 2)
|
|
16
17
|
let boundingBox = measureBoundingBox(offsetLinePath2)
|
|
17
18
|
t.true(comparePoints(boundingBox, [[2, 0, 0], [2, 10, 0]]), 'Unexpected bounding box: ' + JSON.stringify(boundingBox))
|
|
@@ -19,6 +20,7 @@ test('offset: offsetting a straight line produces expected geometry', (t) => {
|
|
|
19
20
|
// offset it by -2.
|
|
20
21
|
offsetLinePath2 = offset({ delta: -2, corners: 'edge', segments: 8 }, linePath2)
|
|
21
22
|
offsetPoints = path2.toPoints(offsetLinePath2)
|
|
23
|
+
t.notThrows(() => path2.validate(offsetLinePath2))
|
|
22
24
|
t.is(offsetPoints.length, 2)
|
|
23
25
|
boundingBox = measureBoundingBox(offsetLinePath2)
|
|
24
26
|
t.true(comparePoints(boundingBox, [[-2, 0, 0], [-2, 10, 0]]), 'Unexpected bounding box: ' + JSON.stringify(boundingBox))
|
|
@@ -27,6 +29,7 @@ test('offset: offsetting a straight line produces expected geometry', (t) => {
|
|
|
27
29
|
linePath2 = path2.fromPoints({ closed: false }, points.reverse())
|
|
28
30
|
offsetLinePath2 = offset({ delta: 2, corners: 'edge', segments: 8 }, linePath2)
|
|
29
31
|
offsetPoints = path2.toPoints(offsetLinePath2)
|
|
32
|
+
t.notThrows(() => path2.validate(offsetLinePath2))
|
|
30
33
|
t.is(offsetPoints.length, 2)
|
|
31
34
|
boundingBox = measureBoundingBox(offsetLinePath2)
|
|
32
35
|
t.true(comparePoints(boundingBox, [[-2, 0, 0], [-2, 10, 0]]), 'Unexpected bounding box: ' + JSON.stringify(boundingBox))
|
|
@@ -39,6 +42,7 @@ test('offset: offsetting a bent line produces expected geometry', (t) => {
|
|
|
39
42
|
// offset it by 2.
|
|
40
43
|
let offsetLinePath2 = offset({ delta: 2, corners: 'edge', segments: 8 }, linePath2)
|
|
41
44
|
let offsetPoints = path2.toPoints(offsetLinePath2)
|
|
45
|
+
t.notThrows(() => path2.validate(offsetLinePath2))
|
|
42
46
|
t.is(offsetPoints.length, 5)
|
|
43
47
|
let boundingBox = measureBoundingBox(offsetLinePath2)
|
|
44
48
|
t.true(comparePoints(boundingBox, [[2, 0, 0], [10, 8, 0]]), 'Unexpected bounding box: ' + JSON.stringify(boundingBox))
|
|
@@ -46,7 +50,8 @@ test('offset: offsetting a bent line produces expected geometry', (t) => {
|
|
|
46
50
|
// offset it by -2.
|
|
47
51
|
offsetLinePath2 = offset({ delta: -2, corners: 'edge', segments: 8 }, linePath2)
|
|
48
52
|
offsetPoints = path2.toPoints(offsetLinePath2)
|
|
49
|
-
t.
|
|
53
|
+
t.notThrows(() => path2.validate(offsetLinePath2))
|
|
54
|
+
t.is(offsetPoints.length, 5)
|
|
50
55
|
boundingBox = measureBoundingBox(offsetLinePath2)
|
|
51
56
|
t.true(comparePoints(boundingBox, [[-2, 0, 0], [10, 12, 0]]), 'Unexpected bounding box: ' + JSON.stringify(boundingBox))
|
|
52
57
|
})
|
|
@@ -56,6 +61,7 @@ test('offset: offsetting a 2 segment straight line produces expected geometry',
|
|
|
56
61
|
const linePath2 = path2.fromPoints({ closed: false }, points)
|
|
57
62
|
const offsetLinePath2 = offset({ delta: 2, corners: 'edge', segments: 8 }, linePath2)
|
|
58
63
|
const offsetPoints = path2.toPoints(offsetLinePath2)
|
|
64
|
+
t.notThrows(() => path2.validate(offsetLinePath2))
|
|
59
65
|
t.is(offsetPoints.length, 3)
|
|
60
66
|
const boundingBox = measureBoundingBox(offsetLinePath2)
|
|
61
67
|
t.true(comparePoints(boundingBox, [[2, 0, 0], [2, 10, 0]]), 'Unexpected bounding box: ' + JSON.stringify(boundingBox))
|
|
@@ -71,6 +77,7 @@ test('offset (corners: chamfer): offset of a path2 produces expected offset path
|
|
|
71
77
|
let pts = path2.toPoints(obs)
|
|
72
78
|
let exp = [
|
|
73
79
|
]
|
|
80
|
+
t.notThrows(() => path2.validate(obs))
|
|
74
81
|
t.true(comparePoints(pts, exp))
|
|
75
82
|
|
|
76
83
|
// expand +
|
|
@@ -82,6 +89,7 @@ test('offset (corners: chamfer): offset of a path2 produces expected offset path
|
|
|
82
89
|
[5.707106781186548, 0.7071067811865475],
|
|
83
90
|
[0.7071067811865475, 5.707106781186548]
|
|
84
91
|
]
|
|
92
|
+
t.notThrows(() => path2.validate(obs))
|
|
85
93
|
t.true(comparePoints(pts, exp))
|
|
86
94
|
|
|
87
95
|
obs = offset({ delta: 1, corners: 'chamfer' }, closeline)
|
|
@@ -94,6 +102,7 @@ test('offset (corners: chamfer): offset of a path2 produces expected offset path
|
|
|
94
102
|
[-1, 5],
|
|
95
103
|
[-1, 6.123233995736766e-17]
|
|
96
104
|
]
|
|
105
|
+
t.notThrows(() => path2.validate(obs))
|
|
97
106
|
t.true(comparePoints(pts, exp))
|
|
98
107
|
|
|
99
108
|
// contract -
|
|
@@ -104,6 +113,7 @@ test('offset (corners: chamfer): offset of a path2 produces expected offset path
|
|
|
104
113
|
[2.5857864376269046, 1],
|
|
105
114
|
[-0.7071067811865475, 4.292893218813452]
|
|
106
115
|
]
|
|
116
|
+
t.notThrows(() => path2.validate(obs))
|
|
107
117
|
t.true(comparePoints(pts, exp))
|
|
108
118
|
|
|
109
119
|
obs = offset({ delta: -1, corners: 'chamfer' }, closeline)
|
|
@@ -113,6 +123,7 @@ test('offset (corners: chamfer): offset of a path2 produces expected offset path
|
|
|
113
123
|
[2.5857864376269046, 1],
|
|
114
124
|
[0.9999999999999996, 2.585786437626905]
|
|
115
125
|
]
|
|
126
|
+
t.notThrows(() => path2.validate(obs))
|
|
116
127
|
t.true(comparePoints(pts, exp))
|
|
117
128
|
})
|
|
118
129
|
|
|
@@ -124,48 +135,30 @@ test('offset (corners: edge): offset of a path2 produces expected offset path2',
|
|
|
124
135
|
let pts = path2.toPoints(obs)
|
|
125
136
|
let exp = [
|
|
126
137
|
[-5, -6],
|
|
127
|
-
[5, -6],
|
|
128
138
|
[6, -6],
|
|
129
|
-
[6, -5],
|
|
130
|
-
[6, 5],
|
|
131
139
|
[6, 6],
|
|
132
|
-
[5, 6],
|
|
133
|
-
[3, 6],
|
|
134
140
|
[2, 6],
|
|
135
|
-
[2, 5],
|
|
136
141
|
[2, 1],
|
|
137
142
|
[-2, 1],
|
|
138
|
-
[-2, 5],
|
|
139
143
|
[-1.9999999999999996, 6],
|
|
140
|
-
[-3, 6],
|
|
141
144
|
[-5, 6]
|
|
142
145
|
]
|
|
146
|
+
t.notThrows(() => path2.validate(obs))
|
|
143
147
|
t.true(comparePoints(pts, exp))
|
|
144
148
|
|
|
145
149
|
obs = offset({ delta: 1, corners: 'edge' }, closeline)
|
|
146
150
|
pts = path2.toPoints(obs)
|
|
147
151
|
exp = [
|
|
148
|
-
[-6, -6],
|
|
149
|
-
[-5, -6],
|
|
150
|
-
[5, -6],
|
|
151
152
|
[6, -6],
|
|
152
|
-
[6, -5],
|
|
153
|
-
[6, 5],
|
|
154
153
|
[6, 6],
|
|
155
|
-
[5, 6],
|
|
156
|
-
[3, 6],
|
|
157
154
|
[2, 6],
|
|
158
|
-
[2, 5],
|
|
159
155
|
[2, 1],
|
|
160
156
|
[-2, 1],
|
|
161
|
-
[-2, 5],
|
|
162
157
|
[-1.9999999999999996, 6],
|
|
163
|
-
[-3, 6],
|
|
164
|
-
[-5, 6],
|
|
165
158
|
[-6, 6],
|
|
166
|
-
[-6,
|
|
167
|
-
[-6, -5]
|
|
159
|
+
[-6, -6]
|
|
168
160
|
]
|
|
161
|
+
t.notThrows(() => path2.validate(obs))
|
|
169
162
|
t.true(comparePoints(pts, exp))
|
|
170
163
|
|
|
171
164
|
obs = offset({ delta: -0.5, corners: 'edge' }, openline)
|
|
@@ -175,15 +168,12 @@ test('offset (corners: edge): offset of a path2 produces expected offset path2',
|
|
|
175
168
|
[4.5, -4.5],
|
|
176
169
|
[4.5, 4.5],
|
|
177
170
|
[3.5, 4.5],
|
|
178
|
-
[3.5, -3.061616997868383e-17],
|
|
179
171
|
[3.4999999999999996, -0.5],
|
|
180
|
-
[3, -0.5],
|
|
181
|
-
[-3, -0.5],
|
|
182
172
|
[-3.5, -0.4999999999999996],
|
|
183
|
-
[-3.5, 3.061616997868383e-17],
|
|
184
173
|
[-3.5, 4.5],
|
|
185
174
|
[-5, 4.5]
|
|
186
175
|
]
|
|
176
|
+
t.notThrows(() => path2.validate(obs))
|
|
187
177
|
t.true(comparePoints(pts, exp))
|
|
188
178
|
|
|
189
179
|
obs = offset({ delta: -0.5, corners: 'edge' }, closeline)
|
|
@@ -193,15 +183,12 @@ test('offset (corners: edge): offset of a path2 produces expected offset path2',
|
|
|
193
183
|
[4.5, -4.5],
|
|
194
184
|
[4.5, 4.5],
|
|
195
185
|
[3.5, 4.5],
|
|
196
|
-
[3.5, -3.061616997868383e-17],
|
|
197
186
|
[3.4999999999999996, -0.5],
|
|
198
|
-
[3, -0.5],
|
|
199
|
-
[-3, -0.5],
|
|
200
187
|
[-3.5, -0.4999999999999996],
|
|
201
|
-
[-3.5, 3.061616997868383e-17],
|
|
202
188
|
[-3.5, 4.5],
|
|
203
189
|
[-4.5, 4.5]
|
|
204
190
|
]
|
|
191
|
+
t.notThrows(() => path2.validate(obs))
|
|
205
192
|
t.true(comparePoints(pts, exp))
|
|
206
193
|
})
|
|
207
194
|
|
|
@@ -237,6 +224,7 @@ test('offset (corners: round): offset of a path2 produces expected offset path2'
|
|
|
237
224
|
[-3, 6],
|
|
238
225
|
[-5, 6]
|
|
239
226
|
]
|
|
227
|
+
t.notThrows(() => path2.validate(obs))
|
|
240
228
|
t.true(comparePoints(pts, exp))
|
|
241
229
|
|
|
242
230
|
obs = offset({ delta: 1, corners: 'round', segments: 16 }, closeline)
|
|
@@ -275,6 +263,7 @@ test('offset (corners: round): offset of a path2 produces expected offset path2'
|
|
|
275
263
|
[-6, 5],
|
|
276
264
|
[-6, -5]
|
|
277
265
|
]
|
|
266
|
+
t.notThrows(() => path2.validate(obs))
|
|
278
267
|
t.true(comparePoints(pts, exp))
|
|
279
268
|
})
|
|
280
269
|
|
|
@@ -317,6 +306,7 @@ test('offset (corners: round): offset of a CW path2 produces expected offset pat
|
|
|
317
306
|
[5, -6],
|
|
318
307
|
[-5, -6]
|
|
319
308
|
]
|
|
309
|
+
t.notThrows(() => path2.validate(obs))
|
|
320
310
|
t.true(comparePoints(pts, exp))
|
|
321
311
|
})
|
|
322
312
|
|
|
@@ -329,6 +319,7 @@ test('offset (options): offsetting of a simple geom2 produces expected offset ge
|
|
|
329
319
|
let pts = geom2.toPoints(obs)
|
|
330
320
|
let exp = [
|
|
331
321
|
]
|
|
322
|
+
t.notThrows(() => geom2.validate(obs))
|
|
332
323
|
t.true(comparePoints(pts, exp))
|
|
333
324
|
|
|
334
325
|
// expand +
|
|
@@ -350,6 +341,7 @@ test('offset (options): offsetting of a simple geom2 produces expected offset ge
|
|
|
350
341
|
[-6, 5],
|
|
351
342
|
[-6, -5]
|
|
352
343
|
]
|
|
344
|
+
t.notThrows(() => geom2.validate(obs))
|
|
353
345
|
t.true(comparePoints(pts, exp))
|
|
354
346
|
|
|
355
347
|
// contract -
|
|
@@ -367,33 +359,23 @@ test('offset (options): offsetting of a simple geom2 produces expected offset ge
|
|
|
367
359
|
[-3.5, 4.5],
|
|
368
360
|
[-4.5, 4.5]
|
|
369
361
|
]
|
|
362
|
+
t.notThrows(() => geom2.validate(obs))
|
|
370
363
|
t.true(comparePoints(pts, exp))
|
|
371
364
|
|
|
372
365
|
// segments 1 - sharp points at corner
|
|
373
366
|
obs = offset({ delta: 1, corners: 'edge' }, geometry)
|
|
374
367
|
pts = geom2.toPoints(obs)
|
|
375
368
|
exp = [
|
|
376
|
-
[-6, -6],
|
|
377
|
-
[-5, -6],
|
|
378
|
-
[5, -6],
|
|
379
369
|
[6, -6],
|
|
380
|
-
[6, -5],
|
|
381
|
-
[6, 5],
|
|
382
370
|
[6, 6],
|
|
383
|
-
[5, 6],
|
|
384
|
-
[3, 6],
|
|
385
371
|
[2, 6],
|
|
386
|
-
[2, 5],
|
|
387
372
|
[2, 1],
|
|
388
373
|
[-2, 1],
|
|
389
|
-
[-2, 5],
|
|
390
374
|
[-1.9999999999999996, 6],
|
|
391
|
-
[-3, 6],
|
|
392
|
-
[-5, 6],
|
|
393
375
|
[-6, 6],
|
|
394
|
-
[-6,
|
|
395
|
-
[-6, -5]
|
|
376
|
+
[-6, -6]
|
|
396
377
|
]
|
|
378
|
+
t.notThrows(() => geom2.validate(obs))
|
|
397
379
|
t.true(comparePoints(pts, exp))
|
|
398
380
|
|
|
399
381
|
// segments 16 - rounded corners
|
|
@@ -417,83 +399,61 @@ test('offset (options): offsetting of a simple geom2 produces expected offset ge
|
|
|
417
399
|
[-3.5, 4.5],
|
|
418
400
|
[-4.5, 4.5]
|
|
419
401
|
]
|
|
402
|
+
t.notThrows(() => geom2.validate(obs))
|
|
420
403
|
t.true(comparePoints(pts, exp))
|
|
421
404
|
})
|
|
422
405
|
|
|
423
406
|
test('offset (options): offsetting of a complex geom2 produces expected offset geom2', (t) => {
|
|
424
407
|
const geometry = geom2.create([
|
|
425
|
-
[[-75
|
|
426
|
-
[[-75
|
|
427
|
-
[[75
|
|
428
|
-
[[-40
|
|
429
|
-
[[75
|
|
430
|
-
[[40
|
|
431
|
-
[[40
|
|
432
|
-
[[-40
|
|
433
|
-
[[15
|
|
434
|
-
[[-15
|
|
435
|
-
[[-15
|
|
436
|
-
[[-8
|
|
437
|
-
[[15
|
|
438
|
-
[[-8
|
|
439
|
-
[[8
|
|
440
|
-
[[8
|
|
441
|
-
[[-2
|
|
442
|
-
[[-2
|
|
443
|
-
[[2
|
|
444
|
-
[[2
|
|
408
|
+
[[-75, 75], [-75, -75]],
|
|
409
|
+
[[-75, -75], [75, -75]],
|
|
410
|
+
[[75, -75], [75, 75]],
|
|
411
|
+
[[-40, 75], [-75, 75]],
|
|
412
|
+
[[75, 75], [40, 75]],
|
|
413
|
+
[[40, 75], [40, 0]],
|
|
414
|
+
[[40, 0], [-40, 0]],
|
|
415
|
+
[[-40, 0], [-40, 75]],
|
|
416
|
+
[[15, -10], [15, -40]],
|
|
417
|
+
[[-15, -10], [15, -10]],
|
|
418
|
+
[[-15, -40], [-15, -10]],
|
|
419
|
+
[[-8, -40], [-15, -40]],
|
|
420
|
+
[[15, -40], [8, -40]],
|
|
421
|
+
[[-8, -25], [-8, -40]],
|
|
422
|
+
[[8, -25], [-8, -25]],
|
|
423
|
+
[[8, -40], [8, -25]],
|
|
424
|
+
[[-2, -15], [-2, -19]],
|
|
425
|
+
[[-2, -19], [2, -19]],
|
|
426
|
+
[[2, -19], [2, -15]],
|
|
427
|
+
[[2, -15], [-2, -15]]
|
|
445
428
|
])
|
|
446
429
|
|
|
447
430
|
// expand +
|
|
448
431
|
const obs = offset({ delta: 2, corners: 'edge' }, geometry)
|
|
449
432
|
const pts = geom2.toPoints(obs)
|
|
450
433
|
const exp = [
|
|
451
|
-
[-77, -77],
|
|
452
|
-
[-75, -77],
|
|
453
|
-
[75, -77],
|
|
454
434
|
[77, -77],
|
|
455
|
-
[77, -75],
|
|
456
|
-
[77, 75],
|
|
457
435
|
[77, 77],
|
|
458
|
-
[75, 77],
|
|
459
|
-
[40, 77],
|
|
460
436
|
[38, 77],
|
|
461
|
-
[38, 75],
|
|
462
437
|
[38, 2],
|
|
463
438
|
[-38, 2],
|
|
464
|
-
[-38, 75],
|
|
465
439
|
[-37.99999999999999, 77],
|
|
466
|
-
[-40, 77],
|
|
467
|
-
[-75, 77],
|
|
468
440
|
[-77, 77],
|
|
469
|
-
[-77, 75],
|
|
470
441
|
[13, -12],
|
|
471
442
|
[13, -38],
|
|
472
443
|
[10, -38],
|
|
473
|
-
[10, -25],
|
|
474
444
|
[10, -23],
|
|
475
|
-
[8, -23],
|
|
476
|
-
[-8, -23],
|
|
477
445
|
[-10, -23],
|
|
478
|
-
[-10, -25],
|
|
479
446
|
[-10, -38],
|
|
480
447
|
[-13, -38],
|
|
481
448
|
[-13, -12],
|
|
482
|
-
[-4, -19],
|
|
483
449
|
[-4, -21],
|
|
484
|
-
[-2, -21],
|
|
485
|
-
[1.9999999999999998, -21],
|
|
486
450
|
[3.9999999999999996, -21],
|
|
487
|
-
[4, -19],
|
|
488
|
-
[4, -15],
|
|
489
451
|
[4, -13],
|
|
490
|
-
[2, -13],
|
|
491
|
-
[-1.9999999999999998, -13],
|
|
492
452
|
[-4, -13],
|
|
493
|
-
[-
|
|
494
|
-
[-77, -75]
|
|
453
|
+
[-77, -77]
|
|
495
454
|
]
|
|
496
|
-
t.
|
|
455
|
+
t.notThrows(() => geom2.validate(obs))
|
|
456
|
+
t.is(pts.length, 20)
|
|
497
457
|
t.true(comparePoints(pts, exp))
|
|
498
458
|
})
|
|
499
459
|
|
|
@@ -537,6 +497,7 @@ test('offset (options): offsetting of round geom2 produces expected offset geom2
|
|
|
537
497
|
[6.7105900605102855, -6.710590060510285],
|
|
538
498
|
[8.767810140100096, -3.6317399864658024]
|
|
539
499
|
]
|
|
500
|
+
t.notThrows(() => geom2.validate(obs))
|
|
540
501
|
t.is(pts.length, 16)
|
|
541
502
|
t.true(comparePoints(pts, exp))
|
|
542
503
|
})
|
|
@@ -34,7 +34,7 @@ const offsetFromPoints = (options, points) => {
|
|
|
34
34
|
delta = Math.abs(delta) // sign is no longer required
|
|
35
35
|
|
|
36
36
|
let previousSegment = null
|
|
37
|
-
|
|
37
|
+
let newPoints = []
|
|
38
38
|
const newCorners = []
|
|
39
39
|
const of = vec2.create()
|
|
40
40
|
const n = points.length
|
|
@@ -94,6 +94,10 @@ const offsetFromPoints = (options, points) => {
|
|
|
94
94
|
// generate corners if necessary
|
|
95
95
|
|
|
96
96
|
if (corners === 'edge') {
|
|
97
|
+
// map for fast point index lookup
|
|
98
|
+
const pointIndex = new Map() // {point: index}
|
|
99
|
+
newPoints.forEach((point, index) => pointIndex.set(point, index))
|
|
100
|
+
|
|
97
101
|
// create edge corners
|
|
98
102
|
const line0 = line2.create()
|
|
99
103
|
const line1 = line2.create()
|
|
@@ -103,16 +107,17 @@ const offsetFromPoints = (options, points) => {
|
|
|
103
107
|
const ip = line2.intersectPointOfLines(line0, line1)
|
|
104
108
|
if (Number.isFinite(ip[0]) && Number.isFinite(ip[1])) {
|
|
105
109
|
const p0 = corner.s0[1]
|
|
106
|
-
|
|
107
|
-
i =
|
|
108
|
-
newPoints
|
|
110
|
+
const i = pointIndex.get(p0)
|
|
111
|
+
newPoints[i] = ip
|
|
112
|
+
newPoints[(i + 1) % newPoints.length] = undefined
|
|
109
113
|
} else {
|
|
110
114
|
// paralell segments, drop one
|
|
111
115
|
const p0 = corner.s1[0]
|
|
112
|
-
const i =
|
|
113
|
-
newPoints
|
|
116
|
+
const i = pointIndex.get(p0)
|
|
117
|
+
newPoints[i] = undefined
|
|
114
118
|
}
|
|
115
119
|
})
|
|
120
|
+
newPoints = newPoints.filter((p) => p !== undefined)
|
|
116
121
|
}
|
|
117
122
|
|
|
118
123
|
if (corners === 'round') {
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const { area } = require('../../../maths/utils')
|
|
2
|
+
const { toOutlines } = require('../../../geometries/geom2')
|
|
3
|
+
const { arePointsInside } = require('../../../geometries/poly2')
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
* Constructs a polygon hierarchy of solids and holes.
|
|
7
|
+
* The hierarchy is represented as a forest of trees. All trees shall be depth at most 2.
|
|
8
|
+
* If a solid exists inside the hole of another solid, it will be split out as its own root.
|
|
9
|
+
*
|
|
10
|
+
* @param {geom2} geometry
|
|
11
|
+
* @returns {Array} an array of polygons with associated holes
|
|
12
|
+
* @alias module:modeling/geometries/geom2.toTree
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* const geometry = subtract(rectangle({size: [5, 5]}), rectangle({size: [3, 3]}))
|
|
16
|
+
* console.log(assignHoles(geometry))
|
|
17
|
+
* [{
|
|
18
|
+
* "solid": [[-2.5,-2.5],[2.5,-2.5],[2.5,2.5],[-2.5,2.5]],
|
|
19
|
+
* "holes": [[[-1.5,1.5],[1.5,1.5],[1.5,-1.5],[-1.5,-1.5]]]
|
|
20
|
+
* }]
|
|
21
|
+
*/
|
|
22
|
+
const assignHoles = (geometry) => {
|
|
23
|
+
const outlines = toOutlines(geometry)
|
|
24
|
+
const solids = [] // solid indices
|
|
25
|
+
const holes = [] // hole indices
|
|
26
|
+
outlines.forEach((outline, i) => {
|
|
27
|
+
const a = area(outline)
|
|
28
|
+
if (a < 0) {
|
|
29
|
+
holes.push(i)
|
|
30
|
+
} else if (a > 0) {
|
|
31
|
+
solids.push(i)
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
// for each hole, determine what solids it is inside of
|
|
36
|
+
const children = [] // child holes of solid[i]
|
|
37
|
+
const parents = [] // parent solids of hole[i]
|
|
38
|
+
solids.forEach((s, i) => {
|
|
39
|
+
const solid = outlines[s]
|
|
40
|
+
children[i] = []
|
|
41
|
+
holes.forEach((h, j) => {
|
|
42
|
+
const hole = outlines[h]
|
|
43
|
+
// check if a point of hole j is inside solid i
|
|
44
|
+
if (arePointsInside([hole[0]], { vertices: solid })) {
|
|
45
|
+
children[i].push(h)
|
|
46
|
+
if (!parents[j]) parents[j] = []
|
|
47
|
+
parents[j].push(i)
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
// check if holes have multiple parents and choose one with fewest children
|
|
53
|
+
holes.forEach((h, j) => {
|
|
54
|
+
// ensure at least one parent exists
|
|
55
|
+
if (parents[j] && parents[j].length > 1) {
|
|
56
|
+
// the solid directly containing this hole
|
|
57
|
+
const directParent = minIndex(parents[j], (p) => children[p].length)
|
|
58
|
+
parents[j].forEach((p, i) => {
|
|
59
|
+
if (i !== directParent) {
|
|
60
|
+
// Remove hole from skip level parents
|
|
61
|
+
children[p] = children[p].filter((c) => c !== h)
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
// map indices back to points
|
|
68
|
+
return children.map((holes, i) => ({
|
|
69
|
+
solid: outlines[solids[i]],
|
|
70
|
+
holes: holes.map((h) => outlines[h])
|
|
71
|
+
}))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/*
|
|
75
|
+
* Find the item in the list with smallest score(item).
|
|
76
|
+
* If the list is empty, return undefined.
|
|
77
|
+
*/
|
|
78
|
+
const minIndex = (list, score) => {
|
|
79
|
+
let bestIndex
|
|
80
|
+
let best
|
|
81
|
+
list.forEach((item, index) => {
|
|
82
|
+
const value = score(item)
|
|
83
|
+
if (best === undefined || value < best) {
|
|
84
|
+
bestIndex = index
|
|
85
|
+
best = value
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
return bestIndex
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = assignHoles
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const test = require('ava')
|
|
2
|
+
|
|
3
|
+
const { subtract, union } = require('../../../operations/booleans')
|
|
4
|
+
const square = require('../../../primitives/square')
|
|
5
|
+
const assignHoles = require('./assignHoles')
|
|
6
|
+
|
|
7
|
+
test('slice: assignHoles() should return a polygon hierarchy', (t) => {
|
|
8
|
+
const exp1 = [{
|
|
9
|
+
solid: [
|
|
10
|
+
[-3.000013333333334, -3.000013333333334],
|
|
11
|
+
[3.000013333333334, -3.000013333333334],
|
|
12
|
+
[3.000013333333334, 3.000013333333334],
|
|
13
|
+
[-3.000013333333334, 3.000013333333334]
|
|
14
|
+
],
|
|
15
|
+
holes: [[
|
|
16
|
+
[-1.9999933333333335, 1.9999933333333335],
|
|
17
|
+
[1.9999933333333335, 1.9999933333333335],
|
|
18
|
+
[1.9999933333333335, -1.9999933333333335],
|
|
19
|
+
[-1.9999933333333335, -1.9999933333333335]
|
|
20
|
+
]]
|
|
21
|
+
}]
|
|
22
|
+
const geometry = subtract(
|
|
23
|
+
square({ size: 6 }),
|
|
24
|
+
square({ size: 4 })
|
|
25
|
+
)
|
|
26
|
+
const obs1 = assignHoles(geometry)
|
|
27
|
+
t.deepEqual(obs1, exp1)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test('slice: assignHoles() should handle nested holes', (t) => {
|
|
31
|
+
const geometry = union(
|
|
32
|
+
subtract(
|
|
33
|
+
square({ size: 6 }),
|
|
34
|
+
square({ size: 4 })
|
|
35
|
+
),
|
|
36
|
+
subtract(
|
|
37
|
+
square({ size: 10 }),
|
|
38
|
+
square({ size: 8 })
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
const obs1 = assignHoles(geometry)
|
|
42
|
+
|
|
43
|
+
const exp1 = [
|
|
44
|
+
{
|
|
45
|
+
solid: [
|
|
46
|
+
[-3.0000006060444444, -3.0000006060444444],
|
|
47
|
+
[3.0000006060444444, -3.0000006060444444],
|
|
48
|
+
[3.0000006060444444, 3.0000006060444444],
|
|
49
|
+
[-3.0000006060444444, 3.0000006060444444]
|
|
50
|
+
],
|
|
51
|
+
holes: [[
|
|
52
|
+
[-2.0000248485333336, 2.0000248485333336],
|
|
53
|
+
[2.0000248485333336, 2.0000248485333336],
|
|
54
|
+
[2.0000248485333336, -2.0000248485333336],
|
|
55
|
+
[-2.0000248485333336, -2.0000248485333336]
|
|
56
|
+
]]
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
solid: [
|
|
60
|
+
[-5.000025454577778, -5.000025454577778],
|
|
61
|
+
[5.000025454577778, -5.000025454577778],
|
|
62
|
+
[5.000025454577778, 5.000025454577778],
|
|
63
|
+
[-5.000025454577778, 5.000025454577778]
|
|
64
|
+
],
|
|
65
|
+
holes: [[
|
|
66
|
+
[-3.9999763635555556, 3.9999763635555556],
|
|
67
|
+
[3.9999763635555556, 3.9999763635555556],
|
|
68
|
+
[3.9999763635555556, -3.9999763635555556],
|
|
69
|
+
[-3.9999763635555556, -3.9999763635555556]
|
|
70
|
+
]]
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
t.deepEqual(obs1, exp1)
|
|
74
|
+
})
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
const { filterPoints, linkedPolygon, locallyInside, splitPolygon } = require('./linkedPolygon')
|
|
2
|
+
const { area, pointInTriangle } = require('./triangle')
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* link every hole into the outer loop, producing a single-ring polygon without holes
|
|
6
|
+
*
|
|
7
|
+
* Original source from https://github.com/mapbox/earcut
|
|
8
|
+
* Copyright (c) 2016 Mapbox
|
|
9
|
+
*/
|
|
10
|
+
const eliminateHoles = (data, holeIndices, outerNode, dim) => {
|
|
11
|
+
const queue = []
|
|
12
|
+
|
|
13
|
+
for (let i = 0, len = holeIndices.length; i < len; i++) {
|
|
14
|
+
const start = holeIndices[i] * dim
|
|
15
|
+
const end = i < len - 1 ? holeIndices[i + 1] * dim : data.length
|
|
16
|
+
const list = linkedPolygon(data, start, end, dim, false)
|
|
17
|
+
if (list === list.next) list.steiner = true
|
|
18
|
+
queue.push(getLeftmost(list))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
queue.sort((a, b) => a.x - b.x) // compare X
|
|
22
|
+
|
|
23
|
+
// process holes from left to right
|
|
24
|
+
for (let i = 0; i < queue.length; i++) {
|
|
25
|
+
outerNode = eliminateHole(queue[i], outerNode)
|
|
26
|
+
outerNode = filterPoints(outerNode, outerNode.next)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return outerNode
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/*
|
|
33
|
+
* find a bridge between vertices that connects hole with an outer ring and link it
|
|
34
|
+
*/
|
|
35
|
+
const eliminateHole = (hole, outerNode) => {
|
|
36
|
+
const bridge = findHoleBridge(hole, outerNode)
|
|
37
|
+
if (!bridge) {
|
|
38
|
+
return outerNode
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const bridgeReverse = splitPolygon(bridge, hole)
|
|
42
|
+
|
|
43
|
+
// filter colinear points around the cuts
|
|
44
|
+
const filteredBridge = filterPoints(bridge, bridge.next)
|
|
45
|
+
filterPoints(bridgeReverse, bridgeReverse.next)
|
|
46
|
+
|
|
47
|
+
// Check if input node was removed by the filtering
|
|
48
|
+
return outerNode === bridge ? filteredBridge : outerNode
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/*
|
|
52
|
+
* David Eberly's algorithm for finding a bridge between hole and outer polygon
|
|
53
|
+
*/
|
|
54
|
+
const findHoleBridge = (hole, outerNode) => {
|
|
55
|
+
let p = outerNode
|
|
56
|
+
const hx = hole.x
|
|
57
|
+
const hy = hole.y
|
|
58
|
+
let qx = -Infinity
|
|
59
|
+
let m
|
|
60
|
+
|
|
61
|
+
// find a segment intersected by a ray from the hole's leftmost point to the left
|
|
62
|
+
// segment's endpoint with lesser x will be potential connection point
|
|
63
|
+
do {
|
|
64
|
+
if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) {
|
|
65
|
+
const x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y)
|
|
66
|
+
if (x <= hx && x > qx) {
|
|
67
|
+
qx = x
|
|
68
|
+
if (x === hx) {
|
|
69
|
+
if (hy === p.y) return p
|
|
70
|
+
if (hy === p.next.y) return p.next
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
m = p.x < p.next.x ? p : p.next
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
p = p.next
|
|
78
|
+
} while (p !== outerNode)
|
|
79
|
+
|
|
80
|
+
if (!m) return null
|
|
81
|
+
|
|
82
|
+
if (hx === qx) return m // hole touches outer segment; pick leftmost endpoint
|
|
83
|
+
|
|
84
|
+
// look for points inside the triangle of hole point, segment intersection and endpoint
|
|
85
|
+
// if there are no points found, we have a valid connection
|
|
86
|
+
// otherwise choose the point of the minimum angle with the ray as connection point
|
|
87
|
+
|
|
88
|
+
const stop = m
|
|
89
|
+
const mx = m.x
|
|
90
|
+
const my = m.y
|
|
91
|
+
let tanMin = Infinity
|
|
92
|
+
|
|
93
|
+
p = m
|
|
94
|
+
|
|
95
|
+
do {
|
|
96
|
+
if (hx >= p.x && p.x >= mx && hx !== p.x &&
|
|
97
|
+
pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) {
|
|
98
|
+
const tan = Math.abs(hy - p.y) / (hx - p.x) // tangential
|
|
99
|
+
|
|
100
|
+
if (locallyInside(p, hole) && (tan < tanMin || (tan === tanMin && (p.x > m.x || (p.x === m.x && sectorContainsSector(m, p)))))) {
|
|
101
|
+
m = p
|
|
102
|
+
tanMin = tan
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
p = p.next
|
|
107
|
+
} while (p !== stop)
|
|
108
|
+
|
|
109
|
+
return m
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/*
|
|
113
|
+
* whether sector in vertex m contains sector in vertex p in the same coordinates
|
|
114
|
+
*/
|
|
115
|
+
const sectorContainsSector = (m, p) => area(m.prev, m, p.prev) < 0 && area(p.next, m, m.next) < 0
|
|
116
|
+
|
|
117
|
+
/*
|
|
118
|
+
* find the leftmost node of a polygon ring
|
|
119
|
+
*/
|
|
120
|
+
const getLeftmost = (start) => {
|
|
121
|
+
let p = start
|
|
122
|
+
let leftmost = start
|
|
123
|
+
do {
|
|
124
|
+
if (p.x < leftmost.x || (p.x === leftmost.x && p.y < leftmost.y)) leftmost = p
|
|
125
|
+
p = p.next
|
|
126
|
+
} while (p !== start)
|
|
127
|
+
|
|
128
|
+
return leftmost
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
module.exports = eliminateHoles
|