@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
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
const eliminateHoles = require('./eliminateHoles')
|
|
2
|
+
const { removeNode, sortLinked } = require('./linkedList')
|
|
3
|
+
const { cureLocalIntersections, filterPoints, isValidDiagonal, linkedPolygon, splitPolygon } = require('./linkedPolygon')
|
|
4
|
+
const { area, pointInTriangle } = require('./triangle')
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* An implementation of the earcut polygon triangulation algorithm.
|
|
8
|
+
*
|
|
9
|
+
* Original source from https://github.com/mapbox/earcut
|
|
10
|
+
* Copyright (c) 2016 Mapbox
|
|
11
|
+
*
|
|
12
|
+
* @param {data} A flat array of vertex coordinates.
|
|
13
|
+
* @param {holeIndices} An array of hole indices if any.
|
|
14
|
+
* @param {dim} The number of coordinates per vertex in the input array.
|
|
15
|
+
*/
|
|
16
|
+
const triangulate = (data, holeIndices, dim = 2) => {
|
|
17
|
+
const hasHoles = holeIndices && holeIndices.length
|
|
18
|
+
const outerLen = hasHoles ? holeIndices[0] * dim : data.length
|
|
19
|
+
let outerNode = linkedPolygon(data, 0, outerLen, dim, true)
|
|
20
|
+
const triangles = []
|
|
21
|
+
|
|
22
|
+
if (!outerNode || outerNode.next === outerNode.prev) return triangles
|
|
23
|
+
|
|
24
|
+
let minX, minY, maxX, maxY, invSize
|
|
25
|
+
|
|
26
|
+
if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim)
|
|
27
|
+
|
|
28
|
+
// if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
|
|
29
|
+
if (data.length > 80 * dim) {
|
|
30
|
+
minX = maxX = data[0]
|
|
31
|
+
minY = maxY = data[1]
|
|
32
|
+
|
|
33
|
+
for (let i = dim; i < outerLen; i += dim) {
|
|
34
|
+
const x = data[i]
|
|
35
|
+
const y = data[i + 1]
|
|
36
|
+
if (x < minX) minX = x
|
|
37
|
+
if (y < minY) minY = y
|
|
38
|
+
if (x > maxX) maxX = x
|
|
39
|
+
if (y > maxY) maxY = y
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// minX, minY and invSize are later used to transform coords into integers for z-order calculation
|
|
43
|
+
invSize = Math.max(maxX - minX, maxY - minY)
|
|
44
|
+
invSize = invSize !== 0 ? 1 / invSize : 0
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
earcutLinked(outerNode, triangles, dim, minX, minY, invSize)
|
|
48
|
+
|
|
49
|
+
return triangles
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/*
|
|
53
|
+
* main ear slicing loop which triangulates a polygon (given as a linked list)
|
|
54
|
+
*/
|
|
55
|
+
const earcutLinked = (ear, triangles, dim, minX, minY, invSize, pass) => {
|
|
56
|
+
if (!ear) return
|
|
57
|
+
|
|
58
|
+
// interlink polygon nodes in z-order
|
|
59
|
+
if (!pass && invSize) indexCurve(ear, minX, minY, invSize)
|
|
60
|
+
|
|
61
|
+
let stop = ear
|
|
62
|
+
let prev
|
|
63
|
+
let next
|
|
64
|
+
|
|
65
|
+
// iterate through ears, slicing them one by one
|
|
66
|
+
while (ear.prev !== ear.next) {
|
|
67
|
+
prev = ear.prev
|
|
68
|
+
next = ear.next
|
|
69
|
+
|
|
70
|
+
if (invSize ? isEarHashed(ear, minX, minY, invSize) : isEar(ear)) {
|
|
71
|
+
// cut off the triangle
|
|
72
|
+
triangles.push(prev.i / dim)
|
|
73
|
+
triangles.push(ear.i / dim)
|
|
74
|
+
triangles.push(next.i / dim)
|
|
75
|
+
|
|
76
|
+
removeNode(ear)
|
|
77
|
+
|
|
78
|
+
// skipping the next vertex leads to less sliver triangles
|
|
79
|
+
ear = next.next
|
|
80
|
+
stop = next.next
|
|
81
|
+
|
|
82
|
+
continue
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
ear = next
|
|
86
|
+
|
|
87
|
+
// if we looped through the whole remaining polygon and can't find any more ears
|
|
88
|
+
if (ear === stop) {
|
|
89
|
+
// try filtering points and slicing again
|
|
90
|
+
if (!pass) {
|
|
91
|
+
earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1)
|
|
92
|
+
|
|
93
|
+
// if this didn't work, try curing all small self-intersections locally
|
|
94
|
+
} else if (pass === 1) {
|
|
95
|
+
ear = cureLocalIntersections(filterPoints(ear), triangles, dim)
|
|
96
|
+
earcutLinked(ear, triangles, dim, minX, minY, invSize, 2)
|
|
97
|
+
|
|
98
|
+
// as a last resort, try splitting the remaining polygon into two
|
|
99
|
+
} else if (pass === 2) {
|
|
100
|
+
splitEarcut(ear, triangles, dim, minX, minY, invSize)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
break
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/*
|
|
109
|
+
* check whether a polygon node forms a valid ear with adjacent nodes
|
|
110
|
+
*/
|
|
111
|
+
const isEar = (ear) => {
|
|
112
|
+
const a = ear.prev
|
|
113
|
+
const b = ear
|
|
114
|
+
const c = ear.next
|
|
115
|
+
|
|
116
|
+
if (area(a, b, c) >= 0) return false // reflex, can't be an ear
|
|
117
|
+
|
|
118
|
+
// now make sure we don't have other points inside the potential ear
|
|
119
|
+
let p = ear.next.next
|
|
120
|
+
|
|
121
|
+
while (p !== ear.prev) {
|
|
122
|
+
if (pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) && area(p.prev, p, p.next) >= 0) {
|
|
123
|
+
return false
|
|
124
|
+
}
|
|
125
|
+
p = p.next
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return true
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const isEarHashed = (ear, minX, minY, invSize) => {
|
|
132
|
+
const a = ear.prev
|
|
133
|
+
const b = ear
|
|
134
|
+
const c = ear.next
|
|
135
|
+
|
|
136
|
+
if (area(a, b, c) >= 0) return false // reflex, can't be an ear
|
|
137
|
+
|
|
138
|
+
// triangle bbox; min & max are calculated like this for speed
|
|
139
|
+
const minTX = a.x < b.x ? (a.x < c.x ? a.x : c.x) : (b.x < c.x ? b.x : c.x)
|
|
140
|
+
const minTY = a.y < b.y ? (a.y < c.y ? a.y : c.y) : (b.y < c.y ? b.y : c.y)
|
|
141
|
+
const maxTX = a.x > b.x ? (a.x > c.x ? a.x : c.x) : (b.x > c.x ? b.x : c.x)
|
|
142
|
+
const maxTY = a.y > b.y ? (a.y > c.y ? a.y : c.y) : (b.y > c.y ? b.y : c.y)
|
|
143
|
+
|
|
144
|
+
// z-order range for the current triangle bbox
|
|
145
|
+
const minZ = zOrder(minTX, minTY, minX, minY, invSize)
|
|
146
|
+
const maxZ = zOrder(maxTX, maxTY, minX, minY, invSize)
|
|
147
|
+
|
|
148
|
+
let p = ear.prevZ
|
|
149
|
+
let n = ear.nextZ
|
|
150
|
+
|
|
151
|
+
// look for points inside the triangle in both directions
|
|
152
|
+
while (p && p.z >= minZ && n && n.z <= maxZ) {
|
|
153
|
+
if (p !== ear.prev && p !== ear.next &&
|
|
154
|
+
pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
|
|
155
|
+
area(p.prev, p, p.next) >= 0) return false
|
|
156
|
+
p = p.prevZ
|
|
157
|
+
|
|
158
|
+
if (n !== ear.prev && n !== ear.next &&
|
|
159
|
+
pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, n.x, n.y) &&
|
|
160
|
+
area(n.prev, n, n.next) >= 0) return false
|
|
161
|
+
n = n.nextZ
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// look for remaining points in decreasing z-order
|
|
165
|
+
while (p && p.z >= minZ) {
|
|
166
|
+
if (p !== ear.prev && p !== ear.next &&
|
|
167
|
+
pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
|
|
168
|
+
area(p.prev, p, p.next) >= 0) return false
|
|
169
|
+
p = p.prevZ
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// look for remaining points in increasing z-order
|
|
173
|
+
while (n && n.z <= maxZ) {
|
|
174
|
+
if (n !== ear.prev && n !== ear.next &&
|
|
175
|
+
pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, n.x, n.y) &&
|
|
176
|
+
area(n.prev, n, n.next) >= 0) return false
|
|
177
|
+
n = n.nextZ
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return true
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/*
|
|
184
|
+
* try splitting polygon into two and triangulate them independently
|
|
185
|
+
*/
|
|
186
|
+
const splitEarcut = (start, triangles, dim, minX, minY, invSize) => {
|
|
187
|
+
// look for a valid diagonal that divides the polygon into two
|
|
188
|
+
let a = start
|
|
189
|
+
do {
|
|
190
|
+
let b = a.next.next
|
|
191
|
+
while (b !== a.prev) {
|
|
192
|
+
if (a.i !== b.i && isValidDiagonal(a, b)) {
|
|
193
|
+
// split the polygon in two by the diagonal
|
|
194
|
+
let c = splitPolygon(a, b)
|
|
195
|
+
|
|
196
|
+
// filter colinear points around the cuts
|
|
197
|
+
a = filterPoints(a, a.next)
|
|
198
|
+
c = filterPoints(c, c.next)
|
|
199
|
+
|
|
200
|
+
// run earcut on each half
|
|
201
|
+
earcutLinked(a, triangles, dim, minX, minY, invSize)
|
|
202
|
+
earcutLinked(c, triangles, dim, minX, minY, invSize)
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
b = b.next
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
a = a.next
|
|
210
|
+
} while (a !== start)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/*
|
|
214
|
+
* interlink polygon nodes in z-order
|
|
215
|
+
*/
|
|
216
|
+
const indexCurve = (start, minX, minY, invSize) => {
|
|
217
|
+
let p = start
|
|
218
|
+
do {
|
|
219
|
+
if (p.z === null) p.z = zOrder(p.x, p.y, minX, minY, invSize)
|
|
220
|
+
p.prevZ = p.prev
|
|
221
|
+
p.nextZ = p.next
|
|
222
|
+
p = p.next
|
|
223
|
+
} while (p !== start)
|
|
224
|
+
|
|
225
|
+
p.prevZ.nextZ = null
|
|
226
|
+
p.prevZ = null
|
|
227
|
+
|
|
228
|
+
sortLinked(p, (p) => p.z)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/*
|
|
232
|
+
* z-order of a point given coords and inverse of the longer side of data bbox
|
|
233
|
+
*/
|
|
234
|
+
const zOrder = (x, y, minX, minY, invSize) => {
|
|
235
|
+
// coords are transformed into non-negative 15-bit integer range
|
|
236
|
+
x = 32767 * (x - minX) * invSize
|
|
237
|
+
y = 32767 * (y - minY) * invSize
|
|
238
|
+
|
|
239
|
+
x = (x | (x << 8)) & 0x00FF00FF
|
|
240
|
+
x = (x | (x << 4)) & 0x0F0F0F0F
|
|
241
|
+
x = (x | (x << 2)) & 0x33333333
|
|
242
|
+
x = (x | (x << 1)) & 0x55555555
|
|
243
|
+
|
|
244
|
+
y = (y | (y << 8)) & 0x00FF00FF
|
|
245
|
+
y = (y | (y << 4)) & 0x0F0F0F0F
|
|
246
|
+
y = (y | (y << 2)) & 0x33333333
|
|
247
|
+
y = (y | (y << 1)) & 0x55555555
|
|
248
|
+
|
|
249
|
+
return x | (y << 1)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
module.exports = triangulate
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const sortLinked = require('./linkedListSort')
|
|
2
|
+
|
|
3
|
+
class Node {
|
|
4
|
+
constructor (i, x, y) {
|
|
5
|
+
// vertex index in coordinates array
|
|
6
|
+
this.i = i
|
|
7
|
+
|
|
8
|
+
// vertex coordinates
|
|
9
|
+
this.x = x
|
|
10
|
+
this.y = y
|
|
11
|
+
|
|
12
|
+
// previous and next vertex nodes in a polygon ring
|
|
13
|
+
this.prev = null
|
|
14
|
+
this.next = null
|
|
15
|
+
|
|
16
|
+
// z-order curve value
|
|
17
|
+
this.z = null
|
|
18
|
+
|
|
19
|
+
// previous and next nodes in z-order
|
|
20
|
+
this.prevZ = null
|
|
21
|
+
this.nextZ = null
|
|
22
|
+
|
|
23
|
+
// indicates whether this is a steiner point
|
|
24
|
+
this.steiner = false
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/*
|
|
29
|
+
* create a node and optionally link it with previous one (in a circular doubly linked list)
|
|
30
|
+
*/
|
|
31
|
+
const insertNode = (i, x, y, last) => {
|
|
32
|
+
const p = new Node(i, x, y)
|
|
33
|
+
|
|
34
|
+
if (!last) {
|
|
35
|
+
p.prev = p
|
|
36
|
+
p.next = p
|
|
37
|
+
} else {
|
|
38
|
+
p.next = last.next
|
|
39
|
+
p.prev = last
|
|
40
|
+
last.next.prev = p
|
|
41
|
+
last.next = p
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return p
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/*
|
|
48
|
+
* remove a node and join prev with next nodes
|
|
49
|
+
*/
|
|
50
|
+
const removeNode = (p) => {
|
|
51
|
+
p.next.prev = p.prev
|
|
52
|
+
p.prev.next = p.next
|
|
53
|
+
|
|
54
|
+
if (p.prevZ) p.prevZ.nextZ = p.nextZ
|
|
55
|
+
if (p.nextZ) p.nextZ.prevZ = p.prevZ
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { Node, insertNode, removeNode, sortLinked }
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
|
|
2
|
+
// Simon Tatham's linked list merge sort algorithm
|
|
3
|
+
// https://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
|
|
4
|
+
const sortLinked = (list, fn) => {
|
|
5
|
+
let i, p, q, e, numMerges
|
|
6
|
+
let inSize = 1
|
|
7
|
+
|
|
8
|
+
do {
|
|
9
|
+
p = list
|
|
10
|
+
list = null
|
|
11
|
+
let tail = null
|
|
12
|
+
numMerges = 0
|
|
13
|
+
|
|
14
|
+
while (p) {
|
|
15
|
+
numMerges++
|
|
16
|
+
q = p
|
|
17
|
+
let pSize = 0
|
|
18
|
+
for (i = 0; i < inSize; i++) {
|
|
19
|
+
pSize++
|
|
20
|
+
q = q.nextZ
|
|
21
|
+
if (!q) break
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let qSize = inSize
|
|
25
|
+
|
|
26
|
+
while (pSize > 0 || (qSize > 0 && q)) {
|
|
27
|
+
if (pSize !== 0 && (qSize === 0 || !q || fn(p) <= fn(q))) {
|
|
28
|
+
e = p
|
|
29
|
+
p = p.nextZ
|
|
30
|
+
pSize--
|
|
31
|
+
} else {
|
|
32
|
+
e = q
|
|
33
|
+
q = q.nextZ
|
|
34
|
+
qSize--
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (tail) tail.nextZ = e
|
|
38
|
+
else list = e
|
|
39
|
+
|
|
40
|
+
e.prevZ = tail
|
|
41
|
+
tail = e
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
p = q
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
tail.nextZ = null
|
|
48
|
+
inSize *= 2
|
|
49
|
+
} while (numMerges > 1)
|
|
50
|
+
|
|
51
|
+
return list
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = sortLinked
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
const { Node, insertNode, removeNode } = require('./linkedList')
|
|
2
|
+
const { area } = require('./triangle')
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* create a circular doubly linked list from polygon points in the specified winding order
|
|
6
|
+
*/
|
|
7
|
+
const linkedPolygon = (data, start, end, dim, clockwise) => {
|
|
8
|
+
let last
|
|
9
|
+
|
|
10
|
+
if (clockwise === (signedArea(data, start, end, dim) > 0)) {
|
|
11
|
+
for (let i = start; i < end; i += dim) {
|
|
12
|
+
last = insertNode(i, data[i], data[i + 1], last)
|
|
13
|
+
}
|
|
14
|
+
} else {
|
|
15
|
+
for (let i = end - dim; i >= start; i -= dim) {
|
|
16
|
+
last = insertNode(i, data[i], data[i + 1], last)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (last && equals(last, last.next)) {
|
|
21
|
+
removeNode(last)
|
|
22
|
+
last = last.next
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return last
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/*
|
|
29
|
+
* eliminate colinear or duplicate points
|
|
30
|
+
*/
|
|
31
|
+
const filterPoints = (start, end) => {
|
|
32
|
+
if (!start) return start
|
|
33
|
+
if (!end) end = start
|
|
34
|
+
|
|
35
|
+
let p = start
|
|
36
|
+
let again
|
|
37
|
+
do {
|
|
38
|
+
again = false
|
|
39
|
+
|
|
40
|
+
if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) {
|
|
41
|
+
removeNode(p)
|
|
42
|
+
p = end = p.prev
|
|
43
|
+
if (p === p.next) break
|
|
44
|
+
again = true
|
|
45
|
+
} else {
|
|
46
|
+
p = p.next
|
|
47
|
+
}
|
|
48
|
+
} while (again || p !== end)
|
|
49
|
+
|
|
50
|
+
return end
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/*
|
|
54
|
+
* go through all polygon nodes and cure small local self-intersections
|
|
55
|
+
*/
|
|
56
|
+
const cureLocalIntersections = (start, triangles, dim) => {
|
|
57
|
+
let p = start
|
|
58
|
+
do {
|
|
59
|
+
const a = p.prev
|
|
60
|
+
const b = p.next.next
|
|
61
|
+
|
|
62
|
+
if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) {
|
|
63
|
+
triangles.push(a.i / dim)
|
|
64
|
+
triangles.push(p.i / dim)
|
|
65
|
+
triangles.push(b.i / dim)
|
|
66
|
+
|
|
67
|
+
// remove two nodes involved
|
|
68
|
+
removeNode(p)
|
|
69
|
+
removeNode(p.next)
|
|
70
|
+
|
|
71
|
+
p = start = b
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
p = p.next
|
|
75
|
+
} while (p !== start)
|
|
76
|
+
|
|
77
|
+
return filterPoints(p)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/*
|
|
81
|
+
* check if a polygon diagonal intersects any polygon segments
|
|
82
|
+
*/
|
|
83
|
+
const intersectsPolygon = (a, b) => {
|
|
84
|
+
let p = a
|
|
85
|
+
do {
|
|
86
|
+
if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i &&
|
|
87
|
+
intersects(p, p.next, a, b)) return true
|
|
88
|
+
p = p.next
|
|
89
|
+
} while (p !== a)
|
|
90
|
+
|
|
91
|
+
return false
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/*
|
|
95
|
+
* check if a polygon diagonal is locally inside the polygon
|
|
96
|
+
*/
|
|
97
|
+
const locallyInside = (a, b) => area(a.prev, a, a.next) < 0
|
|
98
|
+
? area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0
|
|
99
|
+
: area(a, b, a.prev) < 0 || area(a, a.next, b) < 0
|
|
100
|
+
|
|
101
|
+
/*
|
|
102
|
+
* check if the middle point of a polygon diagonal is inside the polygon
|
|
103
|
+
*/
|
|
104
|
+
const middleInside = (a, b) => {
|
|
105
|
+
let p = a
|
|
106
|
+
let inside = false
|
|
107
|
+
const px = (a.x + b.x) / 2
|
|
108
|
+
const py = (a.y + b.y) / 2
|
|
109
|
+
do {
|
|
110
|
+
if (((p.y > py) !== (p.next.y > py)) && p.next.y !== p.y &&
|
|
111
|
+
(px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x)) { inside = !inside }
|
|
112
|
+
p = p.next
|
|
113
|
+
} while (p !== a)
|
|
114
|
+
|
|
115
|
+
return inside
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/*
|
|
119
|
+
* link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two
|
|
120
|
+
* if one belongs to the outer ring and another to a hole, it merges it into a single ring
|
|
121
|
+
*/
|
|
122
|
+
const splitPolygon = (a, b) => {
|
|
123
|
+
const a2 = new Node(a.i, a.x, a.y)
|
|
124
|
+
const b2 = new Node(b.i, b.x, b.y)
|
|
125
|
+
const an = a.next
|
|
126
|
+
const bp = b.prev
|
|
127
|
+
|
|
128
|
+
a.next = b
|
|
129
|
+
b.prev = a
|
|
130
|
+
|
|
131
|
+
a2.next = an
|
|
132
|
+
an.prev = a2
|
|
133
|
+
|
|
134
|
+
b2.next = a2
|
|
135
|
+
a2.prev = b2
|
|
136
|
+
|
|
137
|
+
bp.next = b2
|
|
138
|
+
b2.prev = bp
|
|
139
|
+
|
|
140
|
+
return b2
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/*
|
|
144
|
+
* check if a diagonal between two polygon nodes is valid (lies in polygon interior)
|
|
145
|
+
*/
|
|
146
|
+
const isValidDiagonal = (a, b) => a.next.i !== b.i &&
|
|
147
|
+
a.prev.i !== b.i &&
|
|
148
|
+
!intersectsPolygon(a, b) && // doesn't intersect other edges
|
|
149
|
+
(
|
|
150
|
+
locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible
|
|
151
|
+
(area(a.prev, a, b.prev) || area(a, b.prev, b)) || // does not create opposite-facing sectors
|
|
152
|
+
equals(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
/*
|
|
156
|
+
* check if two segments intersect
|
|
157
|
+
*/
|
|
158
|
+
const intersects = (p1, q1, p2, q2) => {
|
|
159
|
+
const o1 = Math.sign(area(p1, q1, p2))
|
|
160
|
+
const o2 = Math.sign(area(p1, q1, q2))
|
|
161
|
+
const o3 = Math.sign(area(p2, q2, p1))
|
|
162
|
+
const o4 = Math.sign(area(p2, q2, q1))
|
|
163
|
+
|
|
164
|
+
if (o1 !== o2 && o3 !== o4) return true // general case
|
|
165
|
+
|
|
166
|
+
if (o1 === 0 && onSegment(p1, p2, q1)) return true // p1, q1 and p2 are colinear and p2 lies on p1q1
|
|
167
|
+
if (o2 === 0 && onSegment(p1, q2, q1)) return true // p1, q1 and q2 are colinear and q2 lies on p1q1
|
|
168
|
+
if (o3 === 0 && onSegment(p2, p1, q2)) return true // p2, q2 and p1 are colinear and p1 lies on p2q2
|
|
169
|
+
if (o4 === 0 && onSegment(p2, q1, q2)) return true // p2, q2 and q1 are colinear and q1 lies on p2q2
|
|
170
|
+
|
|
171
|
+
return false
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/*
|
|
175
|
+
* for colinear points p, q, r, check if point q lies on segment pr
|
|
176
|
+
*/
|
|
177
|
+
const onSegment = (p, q, r) => q.x <= Math.max(p.x, r.x) &&
|
|
178
|
+
q.x >= Math.min(p.x, r.x) &&
|
|
179
|
+
q.y <= Math.max(p.y, r.y) &&
|
|
180
|
+
q.y >= Math.min(p.y, r.y)
|
|
181
|
+
|
|
182
|
+
const signedArea = (data, start, end, dim) => {
|
|
183
|
+
let sum = 0
|
|
184
|
+
for (let i = start, j = end - dim; i < end; i += dim) {
|
|
185
|
+
sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1])
|
|
186
|
+
j = i
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return sum
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/*
|
|
193
|
+
* check if two points are equal
|
|
194
|
+
*/
|
|
195
|
+
const equals = (p1, p2) => p1.x === p2.x && p1.y === p2.y
|
|
196
|
+
|
|
197
|
+
module.exports = { cureLocalIntersections, filterPoints, isValidDiagonal, linkedPolygon, locallyInside, splitPolygon }
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const geom2 = require('../../../geometries/geom2')
|
|
2
|
+
const plane = require('../../../maths/plane')
|
|
3
|
+
const vec2 = require('../../../maths/vec2')
|
|
4
|
+
const vec3 = require('../../../maths/vec3')
|
|
5
|
+
const calculatePlane = require('../slice/calculatePlane')
|
|
6
|
+
const assignHoles = require('./assignHoles')
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* Constructs a polygon hierarchy which associates holes with their outer solids.
|
|
10
|
+
* This class maps a 3D polygon onto a 2D space using an orthonormal basis.
|
|
11
|
+
* It tracks the mapping so that points can be reversed back to 3D losslessly.
|
|
12
|
+
*/
|
|
13
|
+
class PolygonHierarchy {
|
|
14
|
+
constructor (slice) {
|
|
15
|
+
this.plane = calculatePlane(slice)
|
|
16
|
+
|
|
17
|
+
// create an orthonormal basis
|
|
18
|
+
// choose an arbitrary right hand vector, making sure it is somewhat orthogonal to the plane normal
|
|
19
|
+
const rightvector = vec3.orthogonal(vec3.create(), this.plane)
|
|
20
|
+
const perp = vec3.cross(vec3.create(), this.plane, rightvector)
|
|
21
|
+
this.v = vec3.normalize(perp, perp)
|
|
22
|
+
this.u = vec3.cross(vec3.create(), this.v, this.plane)
|
|
23
|
+
|
|
24
|
+
// map from 2D to original 3D points
|
|
25
|
+
this.basisMap = new Map()
|
|
26
|
+
|
|
27
|
+
// project slice onto 2D plane
|
|
28
|
+
const projected = slice.edges.map((e) => e.map((v) => this.to2D(v)))
|
|
29
|
+
|
|
30
|
+
// compute polygon hierarchies, assign holes to solids
|
|
31
|
+
const geometry = geom2.create(projected)
|
|
32
|
+
this.roots = assignHoles(geometry)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/*
|
|
36
|
+
* project a 3D point onto the 2D plane
|
|
37
|
+
*/
|
|
38
|
+
to2D (vector3) {
|
|
39
|
+
const vector2 = vec2.fromValues(vec3.dot(vector3, this.u), vec3.dot(vector3, this.v))
|
|
40
|
+
this.basisMap.set(vector2, vector3)
|
|
41
|
+
return vector2
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/*
|
|
45
|
+
* un-project a 2D point back into 3D
|
|
46
|
+
*/
|
|
47
|
+
to3D (vector2) {
|
|
48
|
+
// use a map to get the original 3D, no floating point error
|
|
49
|
+
const original = this.basisMap.get(vector2)
|
|
50
|
+
if (original) {
|
|
51
|
+
return original
|
|
52
|
+
} else {
|
|
53
|
+
console.log('Warning: point not in original slice')
|
|
54
|
+
const v1 = vec3.scale(vec3.create(), this.u, vector2[0])
|
|
55
|
+
const v2 = vec3.scale(vec3.create(), this.v, vector2[1])
|
|
56
|
+
|
|
57
|
+
const planeOrigin = vec3.scale(vec3.create(), plane, plane[3])
|
|
58
|
+
const v3 = vec3.add(v1, v1, planeOrigin)
|
|
59
|
+
return vec3.add(v2, v2, v3)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = PolygonHierarchy
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
/*
|
|
3
|
+
* check if a point lies within a convex triangle
|
|
4
|
+
*/
|
|
5
|
+
const pointInTriangle = (ax, ay, bx, by, cx, cy, px, py) => (
|
|
6
|
+
(cx - px) * (ay - py) - (ax - px) * (cy - py) >= 0 &&
|
|
7
|
+
(ax - px) * (by - py) - (bx - px) * (ay - py) >= 0 &&
|
|
8
|
+
(bx - px) * (cy - py) - (cx - px) * (by - py) >= 0
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
* signed area of a triangle
|
|
13
|
+
*/
|
|
14
|
+
const area = (p, q, r) => (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y)
|
|
15
|
+
|
|
16
|
+
module.exports = { area, pointInTriangle }
|
|
@@ -5,6 +5,7 @@ const geom3 = require('../../geometries/geom3')
|
|
|
5
5
|
const poly3 = require('../../geometries/poly3')
|
|
6
6
|
|
|
7
7
|
const slice = require('./slice')
|
|
8
|
+
const repairSlice = require('./slice/repairSlice')
|
|
8
9
|
|
|
9
10
|
const extrudeWalls = require('./extrudeWalls')
|
|
10
11
|
|
|
@@ -25,6 +26,7 @@ const defaultCallback = (progress, index, base) => {
|
|
|
25
26
|
* @param {Boolean} [options.capStart=true] the solid should have a cap at the start
|
|
26
27
|
* @param {Boolean} [options.capEnd=true] the solid should have a cap at the end
|
|
27
28
|
* @param {Boolean} [options.close=false] the solid should have a closing section between start and end
|
|
29
|
+
* @param {Boolean} [options.repair=true] - repair gaps in the geometry
|
|
28
30
|
* @param {Function} [options.callback] the callback function that generates each slice
|
|
29
31
|
* @param {Object} base - the base object which is used to create slices (see the example for callback information)
|
|
30
32
|
* @return {geom3} the extruded shape
|
|
@@ -48,12 +50,18 @@ const extrudeFromSlices = (options, base) => {
|
|
|
48
50
|
capStart: true,
|
|
49
51
|
capEnd: true,
|
|
50
52
|
close: false,
|
|
53
|
+
repair: true,
|
|
51
54
|
callback: defaultCallback
|
|
52
55
|
}
|
|
53
|
-
const { numberOfSlices, capStart, capEnd, close, callback: generate } = Object.assign({ }, defaults, options)
|
|
56
|
+
const { numberOfSlices, capStart, capEnd, close, repair, callback: generate } = Object.assign({ }, defaults, options)
|
|
54
57
|
|
|
55
58
|
if (numberOfSlices < 2) throw new Error('numberOfSlices must be 2 or more')
|
|
56
59
|
|
|
60
|
+
// Repair gaps in the base slice
|
|
61
|
+
if (repair) {
|
|
62
|
+
repairSlice(base)
|
|
63
|
+
}
|
|
64
|
+
|
|
57
65
|
const sMax = numberOfSlices - 1
|
|
58
66
|
|
|
59
67
|
let startSlice = null
|
|
@@ -90,8 +98,7 @@ const extrudeFromSlices = (options, base) => {
|
|
|
90
98
|
}
|
|
91
99
|
if (capStart) {
|
|
92
100
|
// create a cap at the start
|
|
93
|
-
slice.
|
|
94
|
-
const startPolygons = slice.toPolygons(startSlice)
|
|
101
|
+
const startPolygons = slice.toPolygons(startSlice).map(poly3.invert)
|
|
95
102
|
polygons = polygons.concat(startPolygons)
|
|
96
103
|
}
|
|
97
104
|
if (!capStart && !capEnd) {
|