@jscad/modeling 2.8.0 → 2.9.2

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.
Files changed (100) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/jscad-modeling.min.js +433 -391
  3. package/package.json +2 -2
  4. package/src/geometries/geom2/index.d.ts +1 -0
  5. package/src/geometries/geom2/index.js +2 -1
  6. package/src/geometries/geom2/validate.d.ts +3 -0
  7. package/src/geometries/geom2/validate.js +36 -0
  8. package/src/geometries/geom3/index.d.ts +1 -0
  9. package/src/geometries/geom3/index.js +2 -1
  10. package/src/geometries/geom3/isA.js +1 -1
  11. package/src/geometries/geom3/validate.d.ts +3 -0
  12. package/src/geometries/geom3/validate.js +62 -0
  13. package/src/geometries/path2/index.d.ts +1 -0
  14. package/src/geometries/path2/index.js +2 -1
  15. package/src/geometries/path2/validate.d.ts +3 -0
  16. package/src/geometries/path2/validate.js +41 -0
  17. package/src/geometries/poly2/arePointsInside.js +0 -35
  18. package/src/geometries/poly3/index.d.ts +1 -0
  19. package/src/geometries/poly3/index.js +2 -1
  20. package/src/geometries/poly3/invert.js +7 -1
  21. package/src/geometries/poly3/measureArea.test.js +16 -16
  22. package/src/geometries/poly3/measureBoundingSphere.test.js +8 -8
  23. package/src/geometries/poly3/validate.d.ts +4 -0
  24. package/src/geometries/poly3/validate.js +50 -0
  25. package/src/measurements/measureCenterOfMass.test.js +2 -2
  26. package/src/operations/booleans/intersect.test.js +8 -0
  27. package/src/operations/booleans/scission.test.js +4 -4
  28. package/src/operations/booleans/subtract.test.js +8 -0
  29. package/src/operations/booleans/trees/Node.js +10 -16
  30. package/src/operations/booleans/trees/PolygonTreeNode.js +13 -14
  31. package/src/operations/booleans/trees/Tree.js +1 -2
  32. package/src/operations/booleans/trees/splitPolygonByPlane.js +2 -3
  33. package/src/operations/booleans/union.test.js +27 -0
  34. package/src/operations/expansions/expand.test.js +30 -21
  35. package/src/operations/expansions/expandShell.js +2 -2
  36. package/src/operations/expansions/offset.test.js +25 -0
  37. package/src/operations/extrusions/earcut/assignHoles.js +91 -0
  38. package/src/operations/extrusions/earcut/assignHoles.test.js +74 -0
  39. package/src/operations/extrusions/earcut/eliminateHoles.js +131 -0
  40. package/src/operations/extrusions/earcut/index.js +252 -0
  41. package/src/operations/extrusions/earcut/linkedList.js +58 -0
  42. package/src/operations/extrusions/earcut/linkedListSort.js +54 -0
  43. package/src/operations/extrusions/earcut/linkedPolygon.js +197 -0
  44. package/src/operations/extrusions/earcut/polygonHierarchy.js +64 -0
  45. package/src/operations/extrusions/earcut/triangle.js +16 -0
  46. package/src/operations/extrusions/extrudeFromSlices.js +10 -3
  47. package/src/operations/extrusions/extrudeFromSlices.test.js +47 -31
  48. package/src/operations/extrusions/extrudeLinear.js +4 -3
  49. package/src/operations/extrusions/extrudeLinear.test.js +69 -37
  50. package/src/operations/extrusions/extrudeLinearGeom2.js +5 -2
  51. package/src/operations/extrusions/extrudeRectangular.test.js +22 -15
  52. package/src/operations/extrusions/extrudeRotate.test.js +31 -27
  53. package/src/operations/extrusions/project.test.js +5 -5
  54. package/src/operations/extrusions/slice/calculatePlane.js +7 -4
  55. package/src/operations/extrusions/slice/repairSlice.js +47 -0
  56. package/src/operations/extrusions/slice/toPolygons.js +24 -60
  57. package/src/operations/hulls/hull.test.js +24 -1
  58. package/src/operations/hulls/hullChain.test.js +6 -4
  59. package/src/operations/hulls/hullPath2.test.js +1 -1
  60. package/src/operations/modifiers/generalize.test.js +6 -0
  61. package/src/operations/transforms/align.test.js +12 -0
  62. package/src/operations/transforms/center.test.js +12 -0
  63. package/src/operations/transforms/mirror.test.js +16 -0
  64. package/src/operations/transforms/rotate.test.js +10 -0
  65. package/src/operations/transforms/scale.test.js +15 -0
  66. package/src/operations/transforms/transform.test.js +5 -0
  67. package/src/operations/transforms/translate.test.js +16 -0
  68. package/src/primitives/arc.test.js +11 -0
  69. package/src/primitives/circle.test.js +15 -9
  70. package/src/primitives/cube.test.js +3 -0
  71. package/src/primitives/cuboid.test.js +9 -24
  72. package/src/primitives/cylinder.test.js +7 -4
  73. package/src/primitives/cylinderElliptic.js +13 -6
  74. package/src/primitives/cylinderElliptic.test.js +72 -50
  75. package/src/primitives/ellipse.js +3 -1
  76. package/src/primitives/ellipse.test.js +14 -8
  77. package/src/primitives/ellipsoid.js +6 -4
  78. package/src/primitives/ellipsoid.test.js +84 -80
  79. package/src/primitives/geodesicSphere.test.js +3 -0
  80. package/src/primitives/line.test.js +1 -0
  81. package/src/primitives/polygon.test.js +15 -10
  82. package/src/primitives/polyhedron.test.js +14 -42
  83. package/src/primitives/rectangle.test.js +3 -0
  84. package/src/primitives/roundedCuboid.test.js +5 -0
  85. package/src/primitives/roundedCylinder.js +6 -4
  86. package/src/primitives/roundedCylinder.test.js +40 -36
  87. package/src/primitives/roundedRectangle.test.js +5 -0
  88. package/src/primitives/sphere.test.js +52 -73
  89. package/src/primitives/square.test.js +3 -0
  90. package/src/primitives/star.test.js +6 -0
  91. package/src/primitives/torus.test.js +8 -1
  92. package/src/primitives/triangle.test.js +7 -0
  93. package/src/utils/areAllShapesTheSameType.js +2 -2
  94. package/src/utils/areAllShapesTheSameType.test.js +17 -0
  95. package/src/utils/index.d.ts +1 -0
  96. package/src/utils/index.js +3 -1
  97. package/src/utils/trigonometry.d.ts +2 -0
  98. package/src/utils/trigonometry.js +35 -0
  99. package/src/utils/trigonometry.test.js +25 -0
  100. package/test/helpers/nearlyEqual.js +4 -1
@@ -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
@@ -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