@footgun/cobalt 0.3.2 → 0.4.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,494 @@
1
+ 'use strict'
2
+
3
+ module.exports = boxIntersectIter
4
+
5
+ var pool = require('typedarray-pool')
6
+ var bits = require('bit-twiddle')
7
+ var bruteForce = require('./brute')
8
+ var bruteForcePartial = bruteForce.partial
9
+ var bruteForceFull = bruteForce.full
10
+ var sweep = require('./sweep')
11
+ var findMedian = require('./median')
12
+ var genPartition = require('./partition')
13
+
14
+ //Twiddle parameters
15
+ var BRUTE_FORCE_CUTOFF = 128 //Cut off for brute force search
16
+ var SCAN_CUTOFF = (1<<22) //Cut off for two way scan
17
+ var SCAN_COMPLETE_CUTOFF = (1<<22)
18
+
19
+ //Partition functions
20
+ var partitionInteriorContainsInterval = genPartition(
21
+ '!(lo>=p0)&&!(p1>=hi)',
22
+ ['p0', 'p1'])
23
+
24
+ var partitionStartEqual = genPartition(
25
+ 'lo===p0',
26
+ ['p0'])
27
+
28
+ var partitionStartLessThan = genPartition(
29
+ 'lo<p0',
30
+ ['p0'])
31
+
32
+ var partitionEndLessThanEqual = genPartition(
33
+ 'hi<=p0',
34
+ ['p0'])
35
+
36
+ var partitionContainsPoint = genPartition(
37
+ 'lo<=p0&&p0<=hi',
38
+ ['p0'])
39
+
40
+ var partitionContainsPointProper = genPartition(
41
+ 'lo<p0&&p0<=hi',
42
+ ['p0'])
43
+
44
+ //Frame size for iterative loop
45
+ var IFRAME_SIZE = 6
46
+ var DFRAME_SIZE = 2
47
+
48
+ //Data for box statck
49
+ var INIT_CAPACITY = 1024
50
+ var BOX_ISTACK = pool.mallocInt32(INIT_CAPACITY)
51
+ var BOX_DSTACK = pool.mallocDouble(INIT_CAPACITY)
52
+
53
+ //Initialize iterative loop queue
54
+ function iterInit(d, count) {
55
+ var levels = (8 * bits.log2(count+1) * (d+1))|0
56
+ var maxInts = bits.nextPow2(IFRAME_SIZE*levels)
57
+ if(BOX_ISTACK.length < maxInts) {
58
+ pool.free(BOX_ISTACK)
59
+ BOX_ISTACK = pool.mallocInt32(maxInts)
60
+ }
61
+ var maxDoubles = bits.nextPow2(DFRAME_SIZE*levels)
62
+ if(BOX_DSTACK.length < maxDoubles) {
63
+ pool.free(BOX_DSTACK)
64
+ BOX_DSTACK = pool.mallocDouble(maxDoubles)
65
+ }
66
+ }
67
+
68
+ //Append item to queue
69
+ function iterPush(ptr,
70
+ axis,
71
+ redStart, redEnd,
72
+ blueStart, blueEnd,
73
+ state,
74
+ lo, hi) {
75
+
76
+ var iptr = IFRAME_SIZE * ptr
77
+ BOX_ISTACK[iptr] = axis
78
+ BOX_ISTACK[iptr+1] = redStart
79
+ BOX_ISTACK[iptr+2] = redEnd
80
+ BOX_ISTACK[iptr+3] = blueStart
81
+ BOX_ISTACK[iptr+4] = blueEnd
82
+ BOX_ISTACK[iptr+5] = state
83
+
84
+ var dptr = DFRAME_SIZE * ptr
85
+ BOX_DSTACK[dptr] = lo
86
+ BOX_DSTACK[dptr+1] = hi
87
+ }
88
+
89
+ //Special case: Intersect single point with list of intervals
90
+ function onePointPartial(
91
+ d, axis, visit, flip,
92
+ redStart, redEnd, red, redIndex,
93
+ blueOffset, blue, blueId) {
94
+
95
+ var elemSize = 2 * d
96
+ var bluePtr = blueOffset * elemSize
97
+ var blueX = blue[bluePtr + axis]
98
+
99
+ red_loop:
100
+ for(var i=redStart, redPtr=redStart*elemSize; i<redEnd; ++i, redPtr+=elemSize) {
101
+ var r0 = red[redPtr+axis]
102
+ var r1 = red[redPtr+axis+d]
103
+ if(blueX < r0 || r1 < blueX) {
104
+ continue
105
+ }
106
+ if(flip && blueX === r0) {
107
+ continue
108
+ }
109
+ var redId = redIndex[i]
110
+ for(var j=axis+1; j<d; ++j) {
111
+ var r0 = red[redPtr+j]
112
+ var r1 = red[redPtr+j+d]
113
+ var b0 = blue[bluePtr+j]
114
+ var b1 = blue[bluePtr+j+d]
115
+ if(r1 < b0 || b1 < r0) {
116
+ continue red_loop
117
+ }
118
+ }
119
+ var retval
120
+ if(flip) {
121
+ retval = visit(blueId, redId)
122
+ } else {
123
+ retval = visit(redId, blueId)
124
+ }
125
+ if(retval !== void 0) {
126
+ return retval
127
+ }
128
+ }
129
+ }
130
+
131
+ //Special case: Intersect one point with list of intervals
132
+ function onePointFull(
133
+ d, axis, visit,
134
+ redStart, redEnd, red, redIndex,
135
+ blueOffset, blue, blueId) {
136
+
137
+ var elemSize = 2 * d
138
+ var bluePtr = blueOffset * elemSize
139
+ var blueX = blue[bluePtr + axis]
140
+
141
+ red_loop:
142
+ for(var i=redStart, redPtr=redStart*elemSize; i<redEnd; ++i, redPtr+=elemSize) {
143
+ var redId = redIndex[i]
144
+ if(redId === blueId) {
145
+ continue
146
+ }
147
+ var r0 = red[redPtr+axis]
148
+ var r1 = red[redPtr+axis+d]
149
+ if(blueX < r0 || r1 < blueX) {
150
+ continue
151
+ }
152
+ for(var j=axis+1; j<d; ++j) {
153
+ var r0 = red[redPtr+j]
154
+ var r1 = red[redPtr+j+d]
155
+ var b0 = blue[bluePtr+j]
156
+ var b1 = blue[bluePtr+j+d]
157
+ if(r1 < b0 || b1 < r0) {
158
+ continue red_loop
159
+ }
160
+ }
161
+ var retval = visit(redId, blueId)
162
+ if(retval !== void 0) {
163
+ return retval
164
+ }
165
+ }
166
+ }
167
+
168
+ //The main box intersection routine
169
+ function boxIntersectIter(
170
+ d, visit, initFull,
171
+ xSize, xBoxes, xIndex,
172
+ ySize, yBoxes, yIndex) {
173
+
174
+ //Reserve memory for stack
175
+ iterInit(d, xSize + ySize)
176
+
177
+ var top = 0
178
+ var elemSize = 2 * d
179
+ var retval
180
+
181
+ iterPush(top++,
182
+ 0,
183
+ 0, xSize,
184
+ 0, ySize,
185
+ initFull ? 16 : 0,
186
+ -Infinity, Infinity)
187
+ if(!initFull) {
188
+ iterPush(top++,
189
+ 0,
190
+ 0, ySize,
191
+ 0, xSize,
192
+ 1,
193
+ -Infinity, Infinity)
194
+ }
195
+
196
+ while(top > 0) {
197
+ top -= 1
198
+
199
+ var iptr = top * IFRAME_SIZE
200
+ var axis = BOX_ISTACK[iptr]
201
+ var redStart = BOX_ISTACK[iptr+1]
202
+ var redEnd = BOX_ISTACK[iptr+2]
203
+ var blueStart = BOX_ISTACK[iptr+3]
204
+ var blueEnd = BOX_ISTACK[iptr+4]
205
+ var state = BOX_ISTACK[iptr+5]
206
+
207
+ var dptr = top * DFRAME_SIZE
208
+ var lo = BOX_DSTACK[dptr]
209
+ var hi = BOX_DSTACK[dptr+1]
210
+
211
+ //Unpack state info
212
+ var flip = (state & 1)
213
+ var full = !!(state & 16)
214
+
215
+ //Unpack indices
216
+ var red = xBoxes
217
+ var redIndex = xIndex
218
+ var blue = yBoxes
219
+ var blueIndex = yIndex
220
+ if(flip) {
221
+ red = yBoxes
222
+ redIndex = yIndex
223
+ blue = xBoxes
224
+ blueIndex = xIndex
225
+ }
226
+
227
+ if(state & 2) {
228
+ redEnd = partitionStartLessThan(
229
+ d, axis,
230
+ redStart, redEnd, red, redIndex,
231
+ hi)
232
+ if(redStart >= redEnd) {
233
+ continue
234
+ }
235
+ }
236
+ if(state & 4) {
237
+ redStart = partitionEndLessThanEqual(
238
+ d, axis,
239
+ redStart, redEnd, red, redIndex,
240
+ lo)
241
+ if(redStart >= redEnd) {
242
+ continue
243
+ }
244
+ }
245
+
246
+ var redCount = redEnd - redStart
247
+ var blueCount = blueEnd - blueStart
248
+
249
+ if(full) {
250
+ if(d * redCount * (redCount + blueCount) < SCAN_COMPLETE_CUTOFF) {
251
+ retval = sweep.scanComplete(
252
+ d, axis, visit,
253
+ redStart, redEnd, red, redIndex,
254
+ blueStart, blueEnd, blue, blueIndex)
255
+ if(retval !== void 0) {
256
+ return retval
257
+ }
258
+ continue
259
+ }
260
+ } else {
261
+ if(d * Math.min(redCount, blueCount) < BRUTE_FORCE_CUTOFF) {
262
+ //If input small, then use brute force
263
+ retval = bruteForcePartial(
264
+ d, axis, visit, flip,
265
+ redStart, redEnd, red, redIndex,
266
+ blueStart, blueEnd, blue, blueIndex)
267
+ if(retval !== void 0) {
268
+ return retval
269
+ }
270
+ continue
271
+ } else if(d * redCount * blueCount < SCAN_CUTOFF) {
272
+ //If input medium sized, then use sweep and prune
273
+ retval = sweep.scanBipartite(
274
+ d, axis, visit, flip,
275
+ redStart, redEnd, red, redIndex,
276
+ blueStart, blueEnd, blue, blueIndex)
277
+ if(retval !== void 0) {
278
+ return retval
279
+ }
280
+ continue
281
+ }
282
+ }
283
+
284
+ //First, find all red intervals whose interior contains (lo,hi)
285
+ var red0 = partitionInteriorContainsInterval(
286
+ d, axis,
287
+ redStart, redEnd, red, redIndex,
288
+ lo, hi)
289
+
290
+ //Lower dimensional case
291
+ if(redStart < red0) {
292
+
293
+ if(d * (red0 - redStart) < BRUTE_FORCE_CUTOFF) {
294
+ //Special case for small inputs: use brute force
295
+ retval = bruteForceFull(
296
+ d, axis+1, visit,
297
+ redStart, red0, red, redIndex,
298
+ blueStart, blueEnd, blue, blueIndex)
299
+ if(retval !== void 0) {
300
+ return retval
301
+ }
302
+ } else if(axis === d-2) {
303
+ if(flip) {
304
+ retval = sweep.sweepBipartite(
305
+ d, visit,
306
+ blueStart, blueEnd, blue, blueIndex,
307
+ redStart, red0, red, redIndex)
308
+ } else {
309
+ retval = sweep.sweepBipartite(
310
+ d, visit,
311
+ redStart, red0, red, redIndex,
312
+ blueStart, blueEnd, blue, blueIndex)
313
+ }
314
+ if(retval !== void 0) {
315
+ return retval
316
+ }
317
+ } else {
318
+ iterPush(top++,
319
+ axis+1,
320
+ redStart, red0,
321
+ blueStart, blueEnd,
322
+ flip,
323
+ -Infinity, Infinity)
324
+ iterPush(top++,
325
+ axis+1,
326
+ blueStart, blueEnd,
327
+ redStart, red0,
328
+ flip^1,
329
+ -Infinity, Infinity)
330
+ }
331
+ }
332
+
333
+ //Divide and conquer phase
334
+ if(red0 < redEnd) {
335
+
336
+ //Cut blue into 3 parts:
337
+ //
338
+ // Points < mid point
339
+ // Points = mid point
340
+ // Points > mid point
341
+ //
342
+ var blue0 = findMedian(
343
+ d, axis,
344
+ blueStart, blueEnd, blue, blueIndex)
345
+ var mid = blue[elemSize * blue0 + axis]
346
+ var blue1 = partitionStartEqual(
347
+ d, axis,
348
+ blue0, blueEnd, blue, blueIndex,
349
+ mid)
350
+
351
+ //Right case
352
+ if(blue1 < blueEnd) {
353
+ iterPush(top++,
354
+ axis,
355
+ red0, redEnd,
356
+ blue1, blueEnd,
357
+ (flip|4) + (full ? 16 : 0),
358
+ mid, hi)
359
+ }
360
+
361
+ //Left case
362
+ if(blueStart < blue0) {
363
+ iterPush(top++,
364
+ axis,
365
+ red0, redEnd,
366
+ blueStart, blue0,
367
+ (flip|2) + (full ? 16 : 0),
368
+ lo, mid)
369
+ }
370
+
371
+ //Center case (the hard part)
372
+ if(blue0 + 1 === blue1) {
373
+ //Optimization: Range with exactly 1 point, use a brute force scan
374
+ if(full) {
375
+ retval = onePointFull(
376
+ d, axis, visit,
377
+ red0, redEnd, red, redIndex,
378
+ blue0, blue, blueIndex[blue0])
379
+ } else {
380
+ retval = onePointPartial(
381
+ d, axis, visit, flip,
382
+ red0, redEnd, red, redIndex,
383
+ blue0, blue, blueIndex[blue0])
384
+ }
385
+ if(retval !== void 0) {
386
+ return retval
387
+ }
388
+ } else if(blue0 < blue1) {
389
+ var red1
390
+ if(full) {
391
+ //If full intersection, need to handle special case
392
+ red1 = partitionContainsPoint(
393
+ d, axis,
394
+ red0, redEnd, red, redIndex,
395
+ mid)
396
+ if(red0 < red1) {
397
+ var redX = partitionStartEqual(
398
+ d, axis,
399
+ red0, red1, red, redIndex,
400
+ mid)
401
+ if(axis === d-2) {
402
+ //Degenerate sweep intersection:
403
+ // [red0, redX] with [blue0, blue1]
404
+ if(red0 < redX) {
405
+ retval = sweep.sweepComplete(
406
+ d, visit,
407
+ red0, redX, red, redIndex,
408
+ blue0, blue1, blue, blueIndex)
409
+ if(retval !== void 0) {
410
+ return retval
411
+ }
412
+ }
413
+
414
+ //Normal sweep intersection:
415
+ // [redX, red1] with [blue0, blue1]
416
+ if(redX < red1) {
417
+ retval = sweep.sweepBipartite(
418
+ d, visit,
419
+ redX, red1, red, redIndex,
420
+ blue0, blue1, blue, blueIndex)
421
+ if(retval !== void 0) {
422
+ return retval
423
+ }
424
+ }
425
+ } else {
426
+ if(red0 < redX) {
427
+ iterPush(top++,
428
+ axis+1,
429
+ red0, redX,
430
+ blue0, blue1,
431
+ 16,
432
+ -Infinity, Infinity)
433
+ }
434
+ if(redX < red1) {
435
+ iterPush(top++,
436
+ axis+1,
437
+ redX, red1,
438
+ blue0, blue1,
439
+ 0,
440
+ -Infinity, Infinity)
441
+ iterPush(top++,
442
+ axis+1,
443
+ blue0, blue1,
444
+ redX, red1,
445
+ 1,
446
+ -Infinity, Infinity)
447
+ }
448
+ }
449
+ }
450
+ } else {
451
+ if(flip) {
452
+ red1 = partitionContainsPointProper(
453
+ d, axis,
454
+ red0, redEnd, red, redIndex,
455
+ mid)
456
+ } else {
457
+ red1 = partitionContainsPoint(
458
+ d, axis,
459
+ red0, redEnd, red, redIndex,
460
+ mid)
461
+ }
462
+ if(red0 < red1) {
463
+ if(axis === d-2) {
464
+ if(flip) {
465
+ retval = sweep.sweepBipartite(
466
+ d, visit,
467
+ blue0, blue1, blue, blueIndex,
468
+ red0, red1, red, redIndex)
469
+ } else {
470
+ retval = sweep.sweepBipartite(
471
+ d, visit,
472
+ red0, red1, red, redIndex,
473
+ blue0, blue1, blue, blueIndex)
474
+ }
475
+ } else {
476
+ iterPush(top++,
477
+ axis+1,
478
+ red0, red1,
479
+ blue0, blue1,
480
+ flip,
481
+ -Infinity, Infinity)
482
+ iterPush(top++,
483
+ axis+1,
484
+ blue0, blue1,
485
+ red0, red1,
486
+ flip^1,
487
+ -Infinity, Infinity)
488
+ }
489
+ }
490
+ }
491
+ }
492
+ }
493
+ }
494
+ }
@@ -0,0 +1,142 @@
1
+ 'use strict'
2
+
3
+ module.exports = findMedian
4
+
5
+ var genPartition = require('./partition')
6
+
7
+ var partitionStartLessThan = genPartition('lo<p0', ['p0'])
8
+
9
+ var PARTITION_THRESHOLD = 8 //Cut off for using insertion sort in findMedian
10
+
11
+ //Base case for median finding: Use insertion sort
12
+ function insertionSort(d, axis, start, end, boxes, ids) {
13
+ var elemSize = 2 * d
14
+ var boxPtr = elemSize * (start+1) + axis
15
+ for(var i=start+1; i<end; ++i, boxPtr+=elemSize) {
16
+ var x = boxes[boxPtr]
17
+ for(var j=i, ptr=elemSize*(i-1);
18
+ j>start && boxes[ptr+axis] > x;
19
+ --j, ptr-=elemSize) {
20
+ //Swap
21
+ var aPtr = ptr
22
+ var bPtr = ptr+elemSize
23
+ for(var k=0; k<elemSize; ++k, ++aPtr, ++bPtr) {
24
+ var y = boxes[aPtr]
25
+ boxes[aPtr] = boxes[bPtr]
26
+ boxes[bPtr] = y
27
+ }
28
+ var tmp = ids[j]
29
+ ids[j] = ids[j-1]
30
+ ids[j-1] = tmp
31
+ }
32
+ }
33
+ }
34
+
35
+ //Find median using quick select algorithm
36
+ // takes O(n) time with high probability
37
+ function findMedian(d, axis, start, end, boxes, ids) {
38
+ if(end <= start+1) {
39
+ return start
40
+ }
41
+
42
+ var lo = start
43
+ var hi = end
44
+ var mid = ((end + start) >>> 1)
45
+ var elemSize = 2*d
46
+ var pivot = mid
47
+ var value = boxes[elemSize*mid+axis]
48
+
49
+ while(lo < hi) {
50
+ if(hi - lo < PARTITION_THRESHOLD) {
51
+ insertionSort(d, axis, lo, hi, boxes, ids)
52
+ value = boxes[elemSize*mid+axis]
53
+ break
54
+ }
55
+
56
+ //Select pivot using median-of-3
57
+ var count = hi - lo
58
+ var pivot0 = (Math.random()*count+lo)|0
59
+ var value0 = boxes[elemSize*pivot0 + axis]
60
+ var pivot1 = (Math.random()*count+lo)|0
61
+ var value1 = boxes[elemSize*pivot1 + axis]
62
+ var pivot2 = (Math.random()*count+lo)|0
63
+ var value2 = boxes[elemSize*pivot2 + axis]
64
+ if(value0 <= value1) {
65
+ if(value2 >= value1) {
66
+ pivot = pivot1
67
+ value = value1
68
+ } else if(value0 >= value2) {
69
+ pivot = pivot0
70
+ value = value0
71
+ } else {
72
+ pivot = pivot2
73
+ value = value2
74
+ }
75
+ } else {
76
+ if(value1 >= value2) {
77
+ pivot = pivot1
78
+ value = value1
79
+ } else if(value2 >= value0) {
80
+ pivot = pivot0
81
+ value = value0
82
+ } else {
83
+ pivot = pivot2
84
+ value = value2
85
+ }
86
+ }
87
+
88
+ //Swap pivot to end of array
89
+ var aPtr = elemSize * (hi-1)
90
+ var bPtr = elemSize * pivot
91
+ for(var i=0; i<elemSize; ++i, ++aPtr, ++bPtr) {
92
+ var x = boxes[aPtr]
93
+ boxes[aPtr] = boxes[bPtr]
94
+ boxes[bPtr] = x
95
+ }
96
+ var y = ids[hi-1]
97
+ ids[hi-1] = ids[pivot]
98
+ ids[pivot] = y
99
+
100
+ //Partition using pivot
101
+ pivot = partitionStartLessThan(
102
+ d, axis,
103
+ lo, hi-1, boxes, ids,
104
+ value)
105
+
106
+ //Swap pivot back
107
+ var aPtr = elemSize * (hi-1)
108
+ var bPtr = elemSize * pivot
109
+ for(var i=0; i<elemSize; ++i, ++aPtr, ++bPtr) {
110
+ var x = boxes[aPtr]
111
+ boxes[aPtr] = boxes[bPtr]
112
+ boxes[bPtr] = x
113
+ }
114
+ var y = ids[hi-1]
115
+ ids[hi-1] = ids[pivot]
116
+ ids[pivot] = y
117
+
118
+ //Swap pivot to last pivot
119
+ if(mid < pivot) {
120
+ hi = pivot-1
121
+ while(lo < hi &&
122
+ boxes[elemSize*(hi-1)+axis] === value) {
123
+ hi -= 1
124
+ }
125
+ hi += 1
126
+ } else if(pivot < mid) {
127
+ lo = pivot + 1
128
+ while(lo < hi &&
129
+ boxes[elemSize*lo+axis] === value) {
130
+ lo += 1
131
+ }
132
+ } else {
133
+ break
134
+ }
135
+ }
136
+
137
+ //Make sure pivot is at start
138
+ return partitionStartLessThan(
139
+ d, axis,
140
+ start, mid, boxes, ids,
141
+ boxes[elemSize*mid+axis])
142
+ }
@@ -0,0 +1,20 @@
1
+ 'use strict'
2
+
3
+ module.exports = genPartition
4
+
5
+ var code = 'for(var j=2*a,k=j*c,l=k,m=c,n=b,o=a+b,p=c;d>p;++p,k+=j){var _;if($)if(m===p)m+=1,l+=j;else{for(var s=0;j>s;++s){var t=e[k+s];e[k+s]=e[l],e[l++]=t}var u=f[p];f[p]=f[m],f[m++]=u}}return m'
6
+
7
+ function genPartition(predicate, args) {
8
+ var fargs ='abcdef'.split('').concat(args)
9
+ var reads = []
10
+ if(predicate.indexOf('lo') >= 0) {
11
+ reads.push('lo=e[k+n]')
12
+ }
13
+ if(predicate.indexOf('hi') >= 0) {
14
+ reads.push('hi=e[k+o]')
15
+ }
16
+ fargs.push(
17
+ code.replace('_', reads.join())
18
+ .replace('$', predicate))
19
+ return Function.apply(void 0, fargs)
20
+ }