UNPKG

box-intersect

Version:
434 lines (389 loc) 11.3 kB
'use strict' module.exports = { init: sqInit, sweepBipartite: sweepBipartite, sweepComplete: sweepComplete, scanBipartite: scanBipartite, scanComplete: scanComplete } var pool = require('typedarray-pool') var bits = require('bit-twiddle') var isort = require('./sort') //Flag for blue var BLUE_FLAG = (1<<28) //1D sweep event queue stuff (use pool to save space) var INIT_CAPACITY = 1024 var RED_SWEEP_QUEUE = pool.mallocInt32(INIT_CAPACITY) var RED_SWEEP_INDEX = pool.mallocInt32(INIT_CAPACITY) var BLUE_SWEEP_QUEUE = pool.mallocInt32(INIT_CAPACITY) var BLUE_SWEEP_INDEX = pool.mallocInt32(INIT_CAPACITY) var COMMON_SWEEP_QUEUE = pool.mallocInt32(INIT_CAPACITY) var COMMON_SWEEP_INDEX = pool.mallocInt32(INIT_CAPACITY) var SWEEP_EVENTS = pool.mallocDouble(INIT_CAPACITY * 8) //Reserves memory for the 1D sweep data structures function sqInit(count) { var rcount = bits.nextPow2(count) if(RED_SWEEP_QUEUE.length < rcount) { pool.free(RED_SWEEP_QUEUE) RED_SWEEP_QUEUE = pool.mallocInt32(rcount) } if(RED_SWEEP_INDEX.length < rcount) { pool.free(RED_SWEEP_INDEX) RED_SWEEP_INDEX = pool.mallocInt32(rcount) } if(BLUE_SWEEP_QUEUE.length < rcount) { pool.free(BLUE_SWEEP_QUEUE) BLUE_SWEEP_QUEUE = pool.mallocInt32(rcount) } if(BLUE_SWEEP_INDEX.length < rcount) { pool.free(BLUE_SWEEP_INDEX) BLUE_SWEEP_INDEX = pool.mallocInt32(rcount) } if(COMMON_SWEEP_QUEUE.length < rcount) { pool.free(COMMON_SWEEP_QUEUE) COMMON_SWEEP_QUEUE = pool.mallocInt32(rcount) } if(COMMON_SWEEP_INDEX.length < rcount) { pool.free(COMMON_SWEEP_INDEX) COMMON_SWEEP_INDEX = pool.mallocInt32(rcount) } var eventLength = 8 * rcount if(SWEEP_EVENTS.length < eventLength) { pool.free(SWEEP_EVENTS) SWEEP_EVENTS = pool.mallocDouble(eventLength) } } //Remove an item from the active queue in O(1) function sqPop(queue, index, count, item) { var idx = index[item] var top = queue[count-1] queue[idx] = top index[top] = idx } //Insert an item into the active queue in O(1) function sqPush(queue, index, count, item) { queue[count] = item index[item] = count } //Recursion base case: use 1D sweep algorithm function sweepBipartite( d, visit, redStart, redEnd, red, redIndex, blueStart, blueEnd, blue, blueIndex) { //store events as pairs [coordinate, idx] // // red create: -(idx+1) // red destroy: idx // blue create: -(idx+BLUE_FLAG) // blue destroy: idx+BLUE_FLAG // var ptr = 0 var elemSize = 2*d var istart = d-1 var iend = elemSize-1 for(var i=redStart; i<redEnd; ++i) { var idx = redIndex[i] var redOffset = elemSize*i SWEEP_EVENTS[ptr++] = red[redOffset+istart] SWEEP_EVENTS[ptr++] = -(idx+1) SWEEP_EVENTS[ptr++] = red[redOffset+iend] SWEEP_EVENTS[ptr++] = idx } for(var i=blueStart; i<blueEnd; ++i) { var idx = blueIndex[i]+BLUE_FLAG var blueOffset = elemSize*i SWEEP_EVENTS[ptr++] = blue[blueOffset+istart] SWEEP_EVENTS[ptr++] = -idx SWEEP_EVENTS[ptr++] = blue[blueOffset+iend] SWEEP_EVENTS[ptr++] = idx } //process events from left->right var n = ptr >>> 1 isort(SWEEP_EVENTS, n) var redActive = 0 var blueActive = 0 for(var i=0; i<n; ++i) { var e = SWEEP_EVENTS[2*i+1]|0 if(e >= BLUE_FLAG) { //blue destroy event e = (e-BLUE_FLAG)|0 sqPop(BLUE_SWEEP_QUEUE, BLUE_SWEEP_INDEX, blueActive--, e) } else if(e >= 0) { //red destroy event sqPop(RED_SWEEP_QUEUE, RED_SWEEP_INDEX, redActive--, e) } else if(e <= -BLUE_FLAG) { //blue create event e = (-e-BLUE_FLAG)|0 for(var j=0; j<redActive; ++j) { var retval = visit(RED_SWEEP_QUEUE[j], e) if(retval !== void 0) { return retval } } sqPush(BLUE_SWEEP_QUEUE, BLUE_SWEEP_INDEX, blueActive++, e) } else { //red create event e = (-e-1)|0 for(var j=0; j<blueActive; ++j) { var retval = visit(e, BLUE_SWEEP_QUEUE[j]) if(retval !== void 0) { return retval } } sqPush(RED_SWEEP_QUEUE, RED_SWEEP_INDEX, redActive++, e) } } } //Complete sweep function sweepComplete(d, visit, redStart, redEnd, red, redIndex, blueStart, blueEnd, blue, blueIndex) { var ptr = 0 var elemSize = 2*d var istart = d-1 var iend = elemSize-1 for(var i=redStart; i<redEnd; ++i) { var idx = (redIndex[i]+1)<<1 var redOffset = elemSize*i SWEEP_EVENTS[ptr++] = red[redOffset+istart] SWEEP_EVENTS[ptr++] = -idx SWEEP_EVENTS[ptr++] = red[redOffset+iend] SWEEP_EVENTS[ptr++] = idx } for(var i=blueStart; i<blueEnd; ++i) { var idx = (blueIndex[i]+1)<<1 var blueOffset = elemSize*i SWEEP_EVENTS[ptr++] = blue[blueOffset+istart] SWEEP_EVENTS[ptr++] = (-idx)|1 SWEEP_EVENTS[ptr++] = blue[blueOffset+iend] SWEEP_EVENTS[ptr++] = idx|1 } //process events from left->right var n = ptr >>> 1 isort(SWEEP_EVENTS, n) var redActive = 0 var blueActive = 0 var commonActive = 0 for(var i=0; i<n; ++i) { var e = SWEEP_EVENTS[2*i+1]|0 var color = e&1 if(i < n-1 && (e>>1) === (SWEEP_EVENTS[2*i+3]>>1)) { color = 2 i += 1 } if(e < 0) { //Create event var id = -(e>>1) - 1 //Intersect with common for(var j=0; j<commonActive; ++j) { var retval = visit(COMMON_SWEEP_QUEUE[j], id) if(retval !== void 0) { return retval } } if(color !== 0) { //Intersect with red for(var j=0; j<redActive; ++j) { var retval = visit(RED_SWEEP_QUEUE[j], id) if(retval !== void 0) { return retval } } } if(color !== 1) { //Intersect with blue for(var j=0; j<blueActive; ++j) { var retval = visit(BLUE_SWEEP_QUEUE[j], id) if(retval !== void 0) { return retval } } } if(color === 0) { //Red sqPush(RED_SWEEP_QUEUE, RED_SWEEP_INDEX, redActive++, id) } else if(color === 1) { //Blue sqPush(BLUE_SWEEP_QUEUE, BLUE_SWEEP_INDEX, blueActive++, id) } else if(color === 2) { //Both sqPush(COMMON_SWEEP_QUEUE, COMMON_SWEEP_INDEX, commonActive++, id) } } else { //Destroy event var id = (e>>1) - 1 if(color === 0) { //Red sqPop(RED_SWEEP_QUEUE, RED_SWEEP_INDEX, redActive--, id) } else if(color === 1) { //Blue sqPop(BLUE_SWEEP_QUEUE, BLUE_SWEEP_INDEX, blueActive--, id) } else if(color === 2) { //Both sqPop(COMMON_SWEEP_QUEUE, COMMON_SWEEP_INDEX, commonActive--, id) } } } } //Sweep and prune/scanline algorithm: // Scan along axis, detect intersections // Brute force all boxes along axis function scanBipartite( d, axis, visit, flip, redStart, redEnd, red, redIndex, blueStart, blueEnd, blue, blueIndex) { var ptr = 0 var elemSize = 2*d var istart = axis var iend = axis+d var redShift = 1 var blueShift = 1 if(flip) { blueShift = BLUE_FLAG } else { redShift = BLUE_FLAG } for(var i=redStart; i<redEnd; ++i) { var idx = i + redShift var redOffset = elemSize*i SWEEP_EVENTS[ptr++] = red[redOffset+istart] SWEEP_EVENTS[ptr++] = -idx SWEEP_EVENTS[ptr++] = red[redOffset+iend] SWEEP_EVENTS[ptr++] = idx } for(var i=blueStart; i<blueEnd; ++i) { var idx = i + blueShift var blueOffset = elemSize*i SWEEP_EVENTS[ptr++] = blue[blueOffset+istart] SWEEP_EVENTS[ptr++] = -idx } //process events from left->right var n = ptr >>> 1 isort(SWEEP_EVENTS, n) var redActive = 0 for(var i=0; i<n; ++i) { var e = SWEEP_EVENTS[2*i+1]|0 if(e < 0) { var idx = -e var isRed = false if(idx >= BLUE_FLAG) { isRed = !flip idx -= BLUE_FLAG } else { isRed = !!flip idx -= 1 } if(isRed) { sqPush(RED_SWEEP_QUEUE, RED_SWEEP_INDEX, redActive++, idx) } else { var blueId = blueIndex[idx] var bluePtr = elemSize * idx var b0 = blue[bluePtr+axis+1] var b1 = blue[bluePtr+axis+1+d] red_loop: for(var j=0; j<redActive; ++j) { var oidx = RED_SWEEP_QUEUE[j] var redPtr = elemSize * oidx if(b1 < red[redPtr+axis+1] || red[redPtr+axis+1+d] < b0) { continue } for(var k=axis+2; k<d; ++k) { if(blue[bluePtr + k + d] < red[redPtr + k] || red[redPtr + k + d] < blue[bluePtr + k]) { continue red_loop } } var redId = redIndex[oidx] var retval if(flip) { retval = visit(blueId, redId) } else { retval = visit(redId, blueId) } if(retval !== void 0) { return retval } } } } else { sqPop(RED_SWEEP_QUEUE, RED_SWEEP_INDEX, redActive--, e - redShift) } } } function scanComplete( d, axis, visit, redStart, redEnd, red, redIndex, blueStart, blueEnd, blue, blueIndex) { var ptr = 0 var elemSize = 2*d var istart = axis var iend = axis+d for(var i=redStart; i<redEnd; ++i) { var idx = i + BLUE_FLAG var redOffset = elemSize*i SWEEP_EVENTS[ptr++] = red[redOffset+istart] SWEEP_EVENTS[ptr++] = -idx SWEEP_EVENTS[ptr++] = red[redOffset+iend] SWEEP_EVENTS[ptr++] = idx } for(var i=blueStart; i<blueEnd; ++i) { var idx = i + 1 var blueOffset = elemSize*i SWEEP_EVENTS[ptr++] = blue[blueOffset+istart] SWEEP_EVENTS[ptr++] = -idx } //process events from left->right var n = ptr >>> 1 isort(SWEEP_EVENTS, n) var redActive = 0 for(var i=0; i<n; ++i) { var e = SWEEP_EVENTS[2*i+1]|0 if(e < 0) { var idx = -e if(idx >= BLUE_FLAG) { RED_SWEEP_QUEUE[redActive++] = idx - BLUE_FLAG } else { idx -= 1 var blueId = blueIndex[idx] var bluePtr = elemSize * idx var b0 = blue[bluePtr+axis+1] var b1 = blue[bluePtr+axis+1+d] red_loop: for(var j=0; j<redActive; ++j) { var oidx = RED_SWEEP_QUEUE[j] var redId = redIndex[oidx] if(redId === blueId) { break } var redPtr = elemSize * oidx if(b1 < red[redPtr+axis+1] || red[redPtr+axis+1+d] < b0) { continue } for(var k=axis+2; k<d; ++k) { if(blue[bluePtr + k + d] < red[redPtr + k] || red[redPtr + k + d] < blue[bluePtr + k]) { continue red_loop } } var retval = visit(redId, blueId) if(retval !== void 0) { return retval } } } } else { var idx = e - BLUE_FLAG for(var j=redActive-1; j>=0; --j) { if(RED_SWEEP_QUEUE[j] === idx) { for(var k=j+1; k<redActive; ++k) { RED_SWEEP_QUEUE[k-1] = RED_SWEEP_QUEUE[k] } break } } --redActive } } }