@footgun/cobalt 0.3.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,381 @@
1
+ 'use strict'
2
+
3
+ module.exports = cleanPSLG
4
+
5
+ var UnionFind = require('union-find')
6
+ var boxIntersect = require('box-intersect')
7
+ var segseg = require('robust-segment-intersect')
8
+ var rat = require('big-rat')
9
+ var ratCmp = require('big-rat/cmp')
10
+ var ratToFloat = require('big-rat/to-float')
11
+ var ratVec = require('rat-vec')
12
+ var nextafter = require('nextafter')
13
+
14
+ var solveIntersection = require('./lib/rat-seg-intersect')
15
+
16
+ // Bounds on a rational number when rounded to a float
17
+ function boundRat (r) {
18
+ var f = ratToFloat(r)
19
+ return [
20
+ nextafter(f, -Infinity),
21
+ nextafter(f, Infinity)
22
+ ]
23
+ }
24
+
25
+ // Convert a list of edges in a pslg to bounding boxes
26
+ function boundEdges (points, edges) {
27
+ var bounds = new Array(edges.length)
28
+ for (var i = 0; i < edges.length; ++i) {
29
+ var e = edges[i]
30
+ var a = points[e[0]]
31
+ var b = points[e[1]]
32
+ bounds[i] = [
33
+ nextafter(Math.min(a[0], b[0]), -Infinity),
34
+ nextafter(Math.min(a[1], b[1]), -Infinity),
35
+ nextafter(Math.max(a[0], b[0]), Infinity),
36
+ nextafter(Math.max(a[1], b[1]), Infinity)
37
+ ]
38
+ }
39
+ return bounds
40
+ }
41
+
42
+ // Convert a list of points into bounding boxes by duplicating coords
43
+ function boundPoints (points) {
44
+ var bounds = new Array(points.length)
45
+ for (var i = 0; i < points.length; ++i) {
46
+ var p = points[i]
47
+ bounds[i] = [
48
+ nextafter(p[0], -Infinity),
49
+ nextafter(p[1], -Infinity),
50
+ nextafter(p[0], Infinity),
51
+ nextafter(p[1], Infinity)
52
+ ]
53
+ }
54
+ return bounds
55
+ }
56
+
57
+ // Find all pairs of crossing edges in a pslg (given edge bounds)
58
+ function getCrossings (points, edges, edgeBounds) {
59
+ var result = []
60
+ boxIntersect(edgeBounds, function (i, j) {
61
+ var e = edges[i]
62
+ var f = edges[j]
63
+ if (e[0] === f[0] || e[0] === f[1] ||
64
+ e[1] === f[0] || e[1] === f[1]) {
65
+ return
66
+ }
67
+ var a = points[e[0]]
68
+ var b = points[e[1]]
69
+ var c = points[f[0]]
70
+ var d = points[f[1]]
71
+ if (segseg(a, b, c, d)) {
72
+ result.push([i, j])
73
+ }
74
+ })
75
+ return result
76
+ }
77
+
78
+ // Find all pairs of crossing vertices in a pslg (given edge/vert bounds)
79
+ function getTJunctions (points, edges, edgeBounds, vertBounds) {
80
+ var result = []
81
+ boxIntersect(edgeBounds, vertBounds, function (i, v) {
82
+ var e = edges[i]
83
+ if (e[0] === v || e[1] === v) {
84
+ return
85
+ }
86
+ var p = points[v]
87
+ var a = points[e[0]]
88
+ var b = points[e[1]]
89
+ if (segseg(a, b, p, p)) {
90
+ result.push([i, v])
91
+ }
92
+ })
93
+ return result
94
+ }
95
+
96
+ // Cut edges along crossings/tjunctions
97
+ function cutEdges (floatPoints, edges, crossings, junctions, useColor) {
98
+ var i, e
99
+
100
+ // Convert crossings into tjunctions by constructing rational points
101
+ var ratPoints = floatPoints.map(function(p) {
102
+ return [
103
+ rat(p[0]),
104
+ rat(p[1])
105
+ ]
106
+ })
107
+ for (i = 0; i < crossings.length; ++i) {
108
+ var crossing = crossings[i]
109
+ e = crossing[0]
110
+ var f = crossing[1]
111
+ var ee = edges[e]
112
+ var ef = edges[f]
113
+ var x = solveIntersection(
114
+ ratVec(floatPoints[ee[0]]),
115
+ ratVec(floatPoints[ee[1]]),
116
+ ratVec(floatPoints[ef[0]]),
117
+ ratVec(floatPoints[ef[1]]))
118
+ if (!x) {
119
+ // Segments are parallel, should already be handled by t-junctions
120
+ continue
121
+ }
122
+ var idx = floatPoints.length
123
+ floatPoints.push([ratToFloat(x[0]), ratToFloat(x[1])])
124
+ ratPoints.push(x)
125
+ junctions.push([e, idx], [f, idx])
126
+ }
127
+
128
+ // Sort tjunctions
129
+ junctions.sort(function (a, b) {
130
+ if (a[0] !== b[0]) {
131
+ return a[0] - b[0]
132
+ }
133
+ var u = ratPoints[a[1]]
134
+ var v = ratPoints[b[1]]
135
+ return ratCmp(u[0], v[0]) || ratCmp(u[1], v[1])
136
+ })
137
+
138
+ // Split edges along junctions
139
+ for (i = junctions.length - 1; i >= 0; --i) {
140
+ var junction = junctions[i]
141
+ e = junction[0]
142
+
143
+ var edge = edges[e]
144
+ var s = edge[0]
145
+ var t = edge[1]
146
+
147
+ // Check if edge is not lexicographically sorted
148
+ var a = floatPoints[s]
149
+ var b = floatPoints[t]
150
+ if (((a[0] - b[0]) || (a[1] - b[1])) < 0) {
151
+ var tmp = s
152
+ s = t
153
+ t = tmp
154
+ }
155
+
156
+ // Split leading edge
157
+ edge[0] = s
158
+ var last = edge[1] = junction[1]
159
+
160
+ // If we are grouping edges by color, remember to track data
161
+ var color
162
+ if (useColor) {
163
+ color = edge[2]
164
+ }
165
+
166
+ // Split other edges
167
+ while (i > 0 && junctions[i - 1][0] === e) {
168
+ var junction = junctions[--i]
169
+ var next = junction[1]
170
+ if (useColor) {
171
+ edges.push([last, next, color])
172
+ } else {
173
+ edges.push([last, next])
174
+ }
175
+ last = next
176
+ }
177
+
178
+ // Add final edge
179
+ if (useColor) {
180
+ edges.push([last, t, color])
181
+ } else {
182
+ edges.push([last, t])
183
+ }
184
+ }
185
+
186
+ // Return constructed rational points
187
+ return ratPoints
188
+ }
189
+
190
+ // Merge overlapping points
191
+ function dedupPoints (floatPoints, ratPoints, floatBounds) {
192
+ var numPoints = ratPoints.length
193
+ var uf = new UnionFind(numPoints)
194
+
195
+ // Compute rational bounds
196
+ var bounds = []
197
+ for (var i = 0; i < ratPoints.length; ++i) {
198
+ var p = ratPoints[i]
199
+ var xb = boundRat(p[0])
200
+ var yb = boundRat(p[1])
201
+ bounds.push([
202
+ nextafter(xb[0], -Infinity),
203
+ nextafter(yb[0], -Infinity),
204
+ nextafter(xb[1], Infinity),
205
+ nextafter(yb[1], Infinity)
206
+ ])
207
+ }
208
+
209
+ // Link all points with over lapping boxes
210
+ boxIntersect(bounds, function (i, j) {
211
+ uf.link(i, j)
212
+ })
213
+
214
+ // Do 1 pass over points to combine points in label sets
215
+ var noDupes = true
216
+ var labels = new Array(numPoints)
217
+ for (var i = 0; i < numPoints; ++i) {
218
+ var j = uf.find(i)
219
+ if (j !== i) {
220
+ // Clear no-dupes flag, zero out label
221
+ noDupes = false
222
+ // Make each point the top-left point from its cell
223
+ floatPoints[j] = [
224
+ Math.min(floatPoints[i][0], floatPoints[j][0]),
225
+ Math.min(floatPoints[i][1], floatPoints[j][1])
226
+ ]
227
+ }
228
+ }
229
+
230
+ // If no duplicates, return null to signal termination
231
+ if (noDupes) {
232
+ return null
233
+ }
234
+
235
+ var ptr = 0
236
+ for (var i = 0; i < numPoints; ++i) {
237
+ var j = uf.find(i)
238
+ if (j === i) {
239
+ labels[i] = ptr
240
+ floatPoints[ptr++] = floatPoints[i]
241
+ } else {
242
+ labels[i] = -1
243
+ }
244
+ }
245
+
246
+ floatPoints.length = ptr
247
+
248
+ // Do a second pass to fix up missing labels
249
+ for (var i = 0; i < numPoints; ++i) {
250
+ if (labels[i] < 0) {
251
+ labels[i] = labels[uf.find(i)]
252
+ }
253
+ }
254
+
255
+ // Return resulting union-find data structure
256
+ return labels
257
+ }
258
+
259
+ function compareLex2 (a, b) { return (a[0] - b[0]) || (a[1] - b[1]) }
260
+ function compareLex3 (a, b) {
261
+ var d = (a[0] - b[0]) || (a[1] - b[1])
262
+ if (d) {
263
+ return d
264
+ }
265
+ if (a[2] < b[2]) {
266
+ return -1
267
+ } else if (a[2] > b[2]) {
268
+ return 1
269
+ }
270
+ return 0
271
+ }
272
+
273
+ // Remove duplicate edge labels
274
+ function dedupEdges (edges, labels, useColor) {
275
+ if (edges.length === 0) {
276
+ return
277
+ }
278
+ if (labels) {
279
+ for (var i = 0; i < edges.length; ++i) {
280
+ var e = edges[i]
281
+ var a = labels[e[0]]
282
+ var b = labels[e[1]]
283
+ e[0] = Math.min(a, b)
284
+ e[1] = Math.max(a, b)
285
+ }
286
+ } else {
287
+ for (var i = 0; i < edges.length; ++i) {
288
+ var e = edges[i]
289
+ var a = e[0]
290
+ var b = e[1]
291
+ e[0] = Math.min(a, b)
292
+ e[1] = Math.max(a, b)
293
+ }
294
+ }
295
+ if (useColor) {
296
+ edges.sort(compareLex3)
297
+ } else {
298
+ edges.sort(compareLex2)
299
+ }
300
+ var ptr = 1
301
+ for (var i = 1; i < edges.length; ++i) {
302
+ var prev = edges[i - 1]
303
+ var next = edges[i]
304
+ if (next[0] === prev[0] && next[1] === prev[1] &&
305
+ (!useColor || next[2] === prev[2])) {
306
+ continue
307
+ }
308
+ edges[ptr++] = next
309
+ }
310
+ edges.length = ptr
311
+ }
312
+
313
+ function preRound (points, edges, useColor) {
314
+ var labels = dedupPoints(points, [], boundPoints(points))
315
+ dedupEdges(edges, labels, useColor)
316
+ return !!labels
317
+ }
318
+
319
+ // Repeat until convergence
320
+ function snapRound (points, edges, useColor) {
321
+ // 1. find edge crossings
322
+ var edgeBounds = boundEdges(points, edges)
323
+ var crossings = getCrossings(points, edges, edgeBounds)
324
+
325
+ // 2. find t-junctions
326
+ var vertBounds = boundPoints(points)
327
+ var tjunctions = getTJunctions(points, edges, edgeBounds, vertBounds)
328
+
329
+ // 3. cut edges, construct rational points
330
+ var ratPoints = cutEdges(points, edges, crossings, tjunctions, useColor)
331
+
332
+ // 4. dedupe verts
333
+ var labels = dedupPoints(points, ratPoints, vertBounds)
334
+
335
+ // 5. dedupe edges
336
+ dedupEdges(edges, labels, useColor)
337
+
338
+ // 6. check termination
339
+ if (!labels) {
340
+ return (crossings.length > 0 || tjunctions.length > 0)
341
+ }
342
+
343
+ // More iterations necessary
344
+ return true
345
+ }
346
+
347
+ // Main loop, runs PSLG clean up until completion
348
+ function cleanPSLG (points, edges, colors) {
349
+ // If using colors, augment edges with color data
350
+ var prevEdges
351
+ if (colors) {
352
+ prevEdges = edges
353
+ var augEdges = new Array(edges.length)
354
+ for (var i = 0; i < edges.length; ++i) {
355
+ var e = edges[i]
356
+ augEdges[i] = [e[0], e[1], colors[i]]
357
+ }
358
+ edges = augEdges
359
+ }
360
+
361
+ // First round: remove duplicate edges and points
362
+ var modified = preRound(points, edges, !!colors)
363
+
364
+ // Run snap rounding until convergence
365
+ while (snapRound(points, edges, !!colors)) {
366
+ modified = true
367
+ }
368
+
369
+ // Strip color tags
370
+ if (!!colors && modified) {
371
+ prevEdges.length = 0
372
+ colors.length = 0
373
+ for (var i = 0; i < edges.length; ++i) {
374
+ var e = edges[i]
375
+ prevEdges.push([e[0], e[1]])
376
+ colors.push(e[2])
377
+ }
378
+ }
379
+
380
+ return modified
381
+ }
@@ -0,0 +1,42 @@
1
+ 'use strict'
2
+
3
+ module.exports = solveIntersection
4
+
5
+ var ratMul = require('big-rat/mul')
6
+ var ratDiv = require('big-rat/div')
7
+ var ratSub = require('big-rat/sub')
8
+ var ratSign = require('big-rat/sign')
9
+ var rvSub = require('rat-vec/sub')
10
+ var rvAdd = require('rat-vec/add')
11
+ var rvMuls = require('rat-vec/muls')
12
+
13
+ function ratPerp (a, b) {
14
+ return ratSub(ratMul(a[0], b[1]), ratMul(a[1], b[0]))
15
+ }
16
+
17
+ // Solve for intersection
18
+ // x = a + t (b-a)
19
+ // (x - c) ^ (d-c) = 0
20
+ // (t * (b-a) + (a-c) ) ^ (d-c) = 0
21
+ // t * (b-a)^(d-c) = (d-c)^(a-c)
22
+ // t = (d-c)^(a-c) / (b-a)^(d-c)
23
+
24
+ function solveIntersection (a, b, c, d) {
25
+ var ba = rvSub(b, a)
26
+ var dc = rvSub(d, c)
27
+
28
+ var baXdc = ratPerp(ba, dc)
29
+
30
+ if (ratSign(baXdc) === 0) {
31
+ return null
32
+ }
33
+
34
+ var ac = rvSub(a, c)
35
+ var dcXac = ratPerp(dc, ac)
36
+
37
+ var t = ratDiv(dcXac, baXdc)
38
+ var s = rvMuls(ba, t)
39
+ var r = rvAdd(a, s)
40
+
41
+ return r
42
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "clean-pslg",
3
+ "version": "1.1.2",
4
+ "description": "",
5
+ "license": "ISC",
6
+ "author": "",
7
+ "type": "commonjs",
8
+ "private": true,
9
+ "main": "clean-pslg.js",
10
+ "scripts": {
11
+ "test": "echo \"Error: no test specified\" && exit 1"
12
+ },
13
+ "dependencies": {
14
+ "big-rat": "^1.0.3",
15
+ "nextafter": "^1.0.0",
16
+ "rat-vec": "^1.1.1",
17
+ "robust-segment-intersect": "^1.0.1",
18
+ "union-find": "^1.0.2",
19
+ "uniq": "^1.0.1"
20
+ },
21
+ "devDependencies": {
22
+ "canvas-fit": "^1.4.0",
23
+ "mouse-change": "^1.2.1",
24
+ "robust-orientation": "^1.1.3",
25
+ "segment2": "^0.3.2",
26
+ "tape": "^5.9.0",
27
+ "vec2": "^1.6.0"
28
+ }
29
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "poly-to-pslg",
3
+ "version": "1.0.1",
4
+ "type": "module",
5
+ "private": true,
6
+ "description": "",
7
+ "license": "ISC",
8
+ "author": "",
9
+ "main": "poly-to-pslg.js",
10
+ "scripts": {
11
+ "test": "echo \"Error: no test specified\" && exit 1"
12
+ },
13
+ "dependencies": {
14
+ }
15
+ }
@@ -0,0 +1,51 @@
1
+ import cleanPSLG from 'clean-pslg'
2
+
3
+ //Converts a polygon to a planar straight line graph
4
+ export default function polygonToPSLG (loops, options) {
5
+ if(!Array.isArray(loops)) {
6
+ throw new Error('poly-to-pslg: Error, invalid polygon')
7
+ }
8
+ if(loops.length === 0) {
9
+ return {
10
+ points: [],
11
+ edges: []
12
+ }
13
+ }
14
+
15
+ options = options || {}
16
+
17
+ var nested = true
18
+ if('nested' in options) {
19
+ nested = !!options.nested
20
+ } else if(loops[0].length === 2 && typeof loops[0][0] === 'number') {
21
+ //Hack: If use doesn't pass in a loop, then try to guess if it is nested
22
+ nested = false
23
+ }
24
+ if(!nested) {
25
+ loops = [loops]
26
+ }
27
+
28
+ //First we just unroll all the points in the dumb/obvious way
29
+ var points = []
30
+ var edges = []
31
+ for(var i=0; i<loops.length; ++i) {
32
+ var loop = loops[i]
33
+ var offset = points.length
34
+ for(var j=0; j<loop.length; ++j) {
35
+ points.push(loop[j])
36
+ edges.push([ offset+j, offset+(j+1)%loop.length ])
37
+ }
38
+ }
39
+
40
+ //Then we run snap rounding to clean up self intersections and duplicate verts
41
+ var clean = 'clean' in options ? true : !!options.clean
42
+ if(clean) {
43
+ cleanPSLG(points, edges)
44
+ }
45
+
46
+ //Finally, we return the resulting PSLG
47
+ return {
48
+ points: points,
49
+ edges: edges
50
+ }
51
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "typedarray-pool",
3
+ "version": "1.2.0",
4
+ "description": "",
5
+ "license": "ISC",
6
+ "author": "",
7
+ "type": "commonjs",
8
+ "private": true,
9
+ "main": "pool.js",
10
+ "scripts": {
11
+ "test": "echo \"Error: no test specified\" && exit 1"
12
+ },
13
+ "dependencies": {
14
+ "bit-twiddle": "^1.0.2",
15
+ "dup": "^1.0.0"
16
+ }
17
+ }