mirror of
https://github.com/Cola-Echo/memory-manager-concurrent.git
synced 2026-06-06 10:05:51 +00:00
359 lines
11 KiB
JavaScript
359 lines
11 KiB
JavaScript
/**
|
|
*
|
|
* simple-quadtree is a minimal quadtree implementation that supports simple put, get,
|
|
* remove and clear operations on objects having a x, y position and w, h dimension.
|
|
*
|
|
* Copyright (c) 2013 Antti Saarinen <antti.p.saarinen@gmail.com>
|
|
* https://github.com/asaarinen/qtree
|
|
*
|
|
*/
|
|
function QuadTree(x, y, w, h, options) {
|
|
|
|
if( typeof x != 'number' || isNaN(x) )
|
|
x = 0;
|
|
if( typeof y != 'number' || isNaN(y) )
|
|
y = 0;
|
|
if( typeof w != 'number' || isNaN(w) )
|
|
w = 10;
|
|
if( typeof h != 'number' || isNaN(h) )
|
|
h = 10;
|
|
|
|
var maxc = 25;
|
|
var leafratio = 0.5;
|
|
if( options ) {
|
|
if( typeof options.maxchildren == 'number' )
|
|
if( options.maxchildren > 0 )
|
|
maxc = options.maxchildren;
|
|
if( typeof options.leafratio == 'number' )
|
|
if( options.leafratio >= 0 )
|
|
leafratio = options.leafratio;
|
|
}
|
|
|
|
// validate an input object
|
|
function validate(obj) {
|
|
if( !obj )
|
|
return false;
|
|
if( typeof obj.x != 'number' ||
|
|
typeof obj.y != 'number' ||
|
|
typeof obj.w != 'number' ||
|
|
typeof obj.h != 'number' )
|
|
return false;
|
|
if( isNaN(obj.x) || isNaN(obj.y) ||
|
|
isNaN(obj.w) || isNaN(obj.h) )
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
// test for deep equality for x,y,w,h
|
|
function isequal(o1, o2) {
|
|
if( o1.x == o2.x &&
|
|
o1.y == o2.y &&
|
|
o1.w == o2.w &&
|
|
o1.h == o2.h )
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
// create a new quadtree node
|
|
function createnode(x, y, w, h) {
|
|
return {
|
|
x: x,
|
|
y: y,
|
|
w: w,
|
|
h: h,
|
|
c: [],
|
|
l: [],
|
|
n: []
|
|
}
|
|
}
|
|
|
|
// root node used by this quadtree
|
|
var root = createnode(x, y, w, h);
|
|
|
|
// calculate distance between two points
|
|
function distance(x1, y1, x2, y2) {
|
|
return Math.sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
|
|
}
|
|
|
|
// calculate distance between a point and a line (segment)
|
|
function distancePL(x, y, x1, y1, dx1, dy1, len1 ) {
|
|
if( !len1 ) // in case length is not provided, assume a line
|
|
len1 = -1;
|
|
|
|
// x = x1 + s * dx1 + t * dy1
|
|
// y = y1 + s * dy1 - t * dx1
|
|
// x * dy1 - y * dx1 = x1 * dy1 - y1 * dx1 +
|
|
// t * ( dy1 * dy1 + dx1 * dx1 )
|
|
var t = dx1 * dx1 + dy1 * dy1;
|
|
if( t == 0 )
|
|
return null;
|
|
else {
|
|
t = ( x * dy1 - y * dx1 - x1 * dy1 + y1 * dx1 ) / t;
|
|
if( Math.abs(dx1) > Math.abs(dy1) )
|
|
var s = ( x - x1 - t * dy1 ) / dx1;
|
|
else
|
|
var s = ( y - y1 + t * dx1 ) / dy1;
|
|
if( ( s >= 0 && s <= len1 ) || len1 < 0 )
|
|
return {
|
|
s: s,
|
|
t: t,
|
|
x: x1 + s * dx1,
|
|
y: y1 + s * dy1,
|
|
dist: Math.abs(t)
|
|
};
|
|
else if( s < 0 ) {
|
|
var dist = distance(x, y, x1, y1);
|
|
return {
|
|
s: s,
|
|
dist: dist
|
|
};
|
|
} else {
|
|
var dist = distance(x, y,
|
|
x1 + len1*dx1,
|
|
y1 + len1*dy1);
|
|
return {
|
|
s: s,
|
|
dist: dist
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
// does a line and a rectangle overlap ?
|
|
function overlap_line(o1, o2, buf) {
|
|
if( !o1 || !o2 )
|
|
return true;
|
|
var dist = distancePL(o2.x + 0.5 * o2.w,
|
|
o2.y + 0.5 * o2.h,
|
|
o1.x, o1.y, o1.dx, o1.dy, o1.dist);
|
|
if( dist ) {
|
|
dist.dist -= buf;
|
|
if( dist.dist < 0 )
|
|
return true;
|
|
if( dist.dist * dist.dist <= o2.w * o2.w + o2.h * o2.h )
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// do two rectangles overlap ?
|
|
function overlap_rect(o1, o2, buf) {
|
|
if( !o1 || !o2 )
|
|
return true;
|
|
if( o1.x + o1.w < o2.x - buf ||
|
|
o1.y + o1.h < o2.y - buf ||
|
|
o1.x - buf > o2.x + o2.w ||
|
|
o1.y - buf > o2.y + o2.h )
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
function isleaf(node, obj) {
|
|
|
|
var leaf = false;
|
|
if( obj.w * obj.h > node.w * node.h * leafratio )
|
|
leaf = true;
|
|
|
|
if( obj.x < node.x ||
|
|
obj.y < node.y ||
|
|
obj.x + obj.w > node.x + node.w ||
|
|
obj.y + obj.h > node.y + node.h )
|
|
leaf = true;
|
|
|
|
var childnode = null;
|
|
for( var ni = 0; ni < node.n.length; ni++ )
|
|
if( overlap_rect(obj, node.n[ni], 0) ) {
|
|
if( childnode ) { // multiple hits
|
|
leaf = true;
|
|
break;
|
|
} else
|
|
childnode = node.n[ni];
|
|
}
|
|
|
|
return { leaf: leaf,
|
|
childnode: childnode };
|
|
}
|
|
|
|
// put an object to one of the child nodes of this node
|
|
function put_to_nodes(node, obj) {
|
|
var leaf = isleaf(node, obj);
|
|
if( leaf )
|
|
node.l.push(obj);
|
|
else if( leaf.childnode )
|
|
put(leaf.childnode, obj);
|
|
else
|
|
return;
|
|
}
|
|
|
|
// remove an object from this node
|
|
function remove(node, obj, attr) {
|
|
if( !validate(obj) )
|
|
return 0;
|
|
|
|
if( !attr )
|
|
attr = false;
|
|
else if( typeof attr != 'string' )
|
|
attr = 'id';
|
|
|
|
var count = 0;
|
|
for( var ci = 0; ci < node.c.length; ci++ )
|
|
if( ( attr && node.c[ci][attr] == obj[attr] ) ||
|
|
( !attr && isequal(node.c[ci], obj) ) ) {
|
|
count++;
|
|
node.c.splice(ci, 1);
|
|
ci--;
|
|
}
|
|
|
|
for( var ci = 0; ci < node.l.length; ci++ )
|
|
if( ( attr && node.l[ci][attr] == obj[attr] ) ||
|
|
( !attr && isequal(node.l[ci], obj) ) ) {
|
|
count++;
|
|
node.l.splice(ci, 1);
|
|
ci--;
|
|
}
|
|
|
|
var leaf = isleaf(node, obj);
|
|
if( !leaf.leaf && leaf.childnode )
|
|
return count + remove(leaf.childnode, obj, attr);
|
|
return count;
|
|
}
|
|
|
|
// put an object to this node
|
|
function put(node, obj, removeflag) {
|
|
|
|
if( !validate(obj) )
|
|
return;
|
|
|
|
if( node.n.length == 0 ) {
|
|
node.c.push(obj);
|
|
|
|
// subdivide
|
|
if( node.c.length > maxc ) {
|
|
var w2 = node.w / 2;
|
|
var h2 = node.h / 2;
|
|
node.n.push(createnode(node.x, node.y, w2, h2),
|
|
createnode(node.x + w2, node.y, w2, h2),
|
|
createnode(node.x, node.y + h2, w2, h2),
|
|
createnode(node.x + w2, node.y + h2, w2, h2));
|
|
for( var ci = 0; ci < node.c.length; ci++ )
|
|
put_to_nodes(node, node.c[ci]);
|
|
node.c = [];
|
|
}
|
|
} else
|
|
put_to_nodes(node, obj);
|
|
}
|
|
|
|
// iterate through all objects in this node matching given overlap
|
|
// function
|
|
function getter(overlapfun, node, obj, buf, strict, callbackOrArray) {
|
|
for( var li = 0; li < node.l.length; li++ )
|
|
if( !strict || overlapfun(obj, node.l[li], buf) )
|
|
if( typeof callbackOrArray == 'object' )
|
|
callbackOrArray.push(node.l[li]);
|
|
else if( !callbackOrArray(node.l[li]) )
|
|
return false;
|
|
for( var li = 0; li < node.c.length; li++ )
|
|
if( !strict || overlapfun(obj, node.c[li], buf) )
|
|
if( typeof callbackOrArray == 'object' )
|
|
callbackOrArray.push(node.c[li]);
|
|
else if( !callbackOrArray(node.c[li]) )
|
|
return false;
|
|
for( var ni = 0; ni < node.n.length; ni++ ) {
|
|
if( overlapfun(obj, node.n[ni], buf) ) {
|
|
if( typeof callbackOrArray == 'object' )
|
|
callbackOrArray.concat(getter(overlapfun, node.n[ni], obj, buf, strict, callbackOrArray));
|
|
else if( !getter(overlapfun, node.n[ni], obj, buf, strict, callbackOrArray) )
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// iterate through all objects in this node matching the given rectangle
|
|
function get_rect(node, obj, buf, callbackOrArray) {
|
|
return getter(overlap_rect, node, obj, buf, true, callbackOrArray);
|
|
}
|
|
|
|
// iterate through all objects in this node matching the given
|
|
// line (segment)
|
|
function get_line(node, obj, buf, callbackOrArray) {
|
|
return getter(overlap_line, node, obj, buf, false, callbackOrArray);
|
|
}
|
|
|
|
// iterate through all objects in this node matching given
|
|
// geometry, either a rectangle or a line segment
|
|
function get(node, obj, buf, callbackOrArray) {
|
|
|
|
if( typeof buf == 'function' && typeof callbackOrArray == 'undefined' ) {
|
|
callbackOrArray = buf;
|
|
buf = 0;
|
|
}
|
|
if( typeof callbackOrArray == 'undefined' ) {
|
|
callbackOrArray = [];
|
|
buf = 0;
|
|
}
|
|
if( obj == null )
|
|
get_rect(node, obj, buf, callbackOrArray);
|
|
else if( typeof obj.x == 'number' &&
|
|
typeof obj.y == 'number' &&
|
|
!isNaN(obj.x) && !isNaN(obj.y) ) {
|
|
if( typeof obj.dx == 'number' &&
|
|
typeof obj.dy == 'number' &&
|
|
!isNaN(obj.dx) && !isNaN(obj.dy) )
|
|
get_line(node, obj, buf, callbackOrArray);
|
|
else if( typeof obj.w == 'number' &&
|
|
typeof obj.h == 'number' &&
|
|
!isNaN(obj.w) && !isNaN(obj.h) )
|
|
get_rect(node, obj, buf, callbackOrArray);
|
|
}
|
|
if( typeof callbackOrArray == "object" ) return callbackOrArray;
|
|
}
|
|
|
|
// return the object interface
|
|
return {
|
|
get: function(obj, buf, callbackOrArray) {
|
|
return get(root, obj, buf, callbackOrArray);
|
|
},
|
|
put: function(obj) {
|
|
put(root, obj);
|
|
},
|
|
remove: function(obj, attr) {
|
|
return remove(root, obj, attr);
|
|
},
|
|
clear: function() {
|
|
root = createnode(x, y, w, h);
|
|
},
|
|
stringify: function() {
|
|
var strobj = {
|
|
x: x, y: y, w: w, h: h,
|
|
maxc: maxc,
|
|
leafratio: leafratio,
|
|
root: root
|
|
};
|
|
try {
|
|
return JSON.stringify(strobj);
|
|
} catch(err) {
|
|
// could not stringify
|
|
// probably due to objects included in qtree being non-stringifiable
|
|
return null;
|
|
}
|
|
},
|
|
parse: function(str) {
|
|
if( typeof str == 'string' )
|
|
str = JSON.parse(str);
|
|
|
|
x = str.x;
|
|
y = str.y;
|
|
w = str.w;
|
|
h = str.h;
|
|
maxc = str.maxc;
|
|
leafratio = str.leafratio;
|
|
root = str.root;
|
|
}
|
|
};
|
|
}
|
|
|
|
// for use within node.js
|
|
if( typeof module != 'undefined' )
|
|
module.exports = QuadTree;
|