Last active
December 21, 2015 22:42
-
-
Save 06wj/6376555 to your computer and use it in GitHub Desktop.
quad
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//copied and modified from http://www.nshen.net/article/2011-08-25/as3-DataStructure/ | |
var Rectangle = require("./Rectangle"); | |
var QuadTree = function(rect, maxDepth, currentDepth) { | |
this._rect = rect; | |
this._depth = currentDepth || 0; | |
this._maxDepth = maxDepth || 3; | |
this._hw = rect.width >> 1; | |
this._hh = rect.height >> 1; | |
this._midX = rect.x + this._hw; | |
this._midY = rect.y + this._hh; | |
this.clear(); | |
}; | |
QuadTree.prototype.clear = function() { | |
this._children = []; | |
this._topLeft = this._topRight = this._bottomLeft = this._bottomRight = null; | |
}; | |
QuadTree.prototype.toString = function() { | |
var s = "[QuadTreeNode> " | |
var l = this._children.length; | |
s += l > 1 ? (l + " children") : (l + " child") | |
return s; | |
}; | |
/** | |
* 节点按 <左上,右上,左下,右下>输出 | |
*/ | |
QuadTree.prototype.dump = function() { | |
var s = ""; | |
this.preorder(this, function(node){ | |
var d = node._depth; | |
for (var i = 0; i < d; i++) { | |
if (i == d - 1) | |
s += "+---"; | |
else | |
s += "| "; | |
} | |
s += node + "\n"; | |
}); | |
return s; | |
}; | |
QuadTree.prototype.preorder = function(node, process) { | |
process(node); | |
if (node._topLeft) { | |
this.preorder(node._topLeft, process); | |
} | |
if (node._topRight) { | |
this.preorder(node._topRight, process); | |
} | |
if (node._bottomLeft) { | |
this.preorder(node._bottomLeft, process); | |
} | |
if (node._bottomRight) { | |
this.preorder(node._bottomRight, process); | |
} | |
}; | |
// obj {rect:{x, y, width, height}} | |
QuadTree.prototype.retrieve = function(obj) { | |
var r = [] | |
if (!this._topLeft) { | |
r.push.apply(r, this._children) | |
return r; | |
} | |
var rect = obj.rect; | |
var rx = rect.x; | |
var ry = rect.y; | |
var rw = rect.width; | |
var rh = rect.height; | |
var rr = rx + rw; | |
var rb = ry + rh; | |
//如果所取区块比本身区域还大,那么它所有子树的children都取出 | |
if (rx <= this._rect.x && ry <= this._rect.y && rr >= this._rect.right && rb >= this._rect.bottom) { | |
r.push.apply(r, this._children) | |
r.push.apply(r, this._topLeft.retrieve(obj)) | |
r.push.apply(r, this._topRight.retrieve(obj)) | |
r.push.apply(r, this._bottomLeft.retrieve(obj)) | |
r.push.apply(r, this._bottomRight.retrieve(obj)) | |
return r; | |
} | |
//否则就只取对应的区域子树 | |
// 完全在分区里 | |
if ((rx > this._rect.x) && (rr < this._midX)) { | |
if (ry > this._rect.y && rb < this._midY) { | |
r.push.apply(r, this._topLeft.retrieve(obj)) | |
return r; | |
} | |
if (ry > this._midY && rb < this._rect.bottom) { | |
r.push.apply(r, this._bottomLeft.retrieve(obj)) | |
return r; | |
} | |
} | |
if (rx > this._midX && rr < this._rect.right) { | |
if (ry > this._rect.y && rb < this._midY) { | |
r.push.apply(r, this._topRight.retrieve(obj)) | |
return r | |
} | |
if (ry > this._midY && rb < this._rect.bottom) { | |
r.push.apply(r, this._bottomRight.retrieve(obj)) | |
return r | |
} | |
} | |
//只要有部分在分区里,也放到对应分区里,但注意可以重复放 | |
//上边 | |
if (rb > this._rect.y && ry < this._midY) { | |
if (rx < this._midX && rr > this._rect.x) { | |
r.push.apply(r, this._topLeft.retrieve(obj)); | |
} | |
if (rx < this._rect.right && rr > this._midX) { | |
r.push.apply(r, this._topRight.retrieve(obj)); | |
} | |
} | |
// 下边 | |
if (rb > this._midY && ry < this._rect.bottom) { | |
if (rx < this._midX && rr > this._rect.x) { | |
r.push.apply(r, this._bottomLeft.retrieve(obj)); | |
} | |
if (rx < this._rect.right && rr > this._midX) { | |
r.push.apply(r, this._bottomRight.retrieve(obj)); | |
} | |
} | |
return r; | |
}; | |
// obj {rect:{x, y, width, height}} or [obj, obj, obj] | |
QuadTree.prototype.insert = function(obj) { | |
if (Object.prototype.toString.call(obj) === "[object Array]") { | |
for (var i = 0, l = obj.length; i < l; i++) { | |
this.insert(obj[i]); | |
} | |
return; | |
} | |
var rect = obj.rect; | |
var rx = rect.x; | |
var ry = rect.y; | |
var rw = rect.width; | |
var rh = rect.height; | |
var rr = rx + rw; | |
var rb = ry + rh; | |
// 如果不能切分或者obj比整个区域还大,就放到children里 | |
if (this._depth >= this._maxDepth || (rx <= this._rect.x && ry <= this._rect.y && rx + rw >= this._rect.right && ry + rh >= this._rect.bottom)) { | |
this._children.push(obj); | |
return; | |
} | |
if (!this._topLeft) { | |
var d = this._depth + 1 | |
this._topLeft = new QuadTree(new Rectangle(this._rect.x, this._rect.y, this._hw, this._hh), this._maxDepth, d); | |
this._topRight = new QuadTree(new Rectangle(this._rect.x + this._hw, this._rect.y, this._hw, this._hh), this._maxDepth, d); | |
this._bottomLeft = new QuadTree(new Rectangle(this._rect.x, this._rect.y + this._hh, this._hw, this._hh), this._maxDepth, d); | |
this._bottomRight = new QuadTree(new Rectangle(this._rect.x + this._hw, this._rect.y + this._hh, this._hw, this._hh), this._maxDepth, d); | |
} | |
// 可以完全放到分区里就递归放到对应分区里 | |
if ((rx > this._rect.x) && (rr < this._midX)) { | |
if (ry > this._rect.y && rb < this._midY) { | |
this._topLeft.insert(obj); | |
return; | |
} | |
if (ry > this._midY && rb < this._rect.bottom) { | |
this._bottomLeft.insert(obj); | |
return; | |
} | |
} | |
if (rx > this._midX && rr < this._rect.right) { | |
if (ry > this._rect.y && rb < this._midY) { | |
this._topRight.insert(obj); | |
return; | |
} | |
if (ry > this._midY && rb < this._rect.bottom) { | |
this._bottomRight.insert(obj); | |
return; | |
} | |
} | |
//只要有部分在分区里,也放到对应分区里,但注意可以重复放 | |
//上边 | |
if (rb > this._rect.y && ry < this._midY) { | |
if (rx < this._midX && rr > this._rect.x) { | |
this._topLeft.insert(obj); | |
} | |
if (rx < this._rect.right && rr > this._midX) { | |
this._topRight.insert(obj); | |
} | |
} | |
// 下边 | |
if (rb > this._midY && ry < this._rect.bottom) { | |
if (rx < this._midX && rr > this._rect.x) { | |
this._bottomLeft.insert(obj); | |
} | |
if (rx < this._rect.right && rr > this._midX) { | |
this._bottomRight.insert(obj); | |
} | |
} | |
} | |
module.exports = QuadTree; | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var Rectangle = function(x, y, width, height) { | |
this.set(x, y, width, height); | |
}; | |
Rectangle.prototype.set = function(x, y, width, height){ | |
this.x = x; | |
this.y = y; | |
this.width = width; | |
this.height = height; | |
this.bottom = this.y + this.height; | |
this.right = this.x + this.width; | |
}; | |
Rectangle.prototype.intersects = function(rect){ | |
return rect.x <= this.right && this.x <= rect.right&& | |
rect.y <= this.bottom && this.y <= rect.bottom; | |
}; | |
module.exports = Rectangle; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>test</title> | |
</head> | |
<body> | |
<canvas></canvas> | |
<script> | |
var Rectangle = function(x, y, width, height) { | |
this.set(x, y, width, height); | |
}; | |
Rectangle.prototype.set = function(x, y, width, height){ | |
this.x = x; | |
this.y = y; | |
this.width = width; | |
this.height = height; | |
this.bottom = this.y + this.height; | |
this.right = this.x + this.width; | |
}; | |
Rectangle.prototype.intersects = function(rect){ | |
return rect.x <= this.right && this.x <= rect.right&& | |
rect.y <= this.bottom && this.y <= rect.bottom; | |
}; | |
var QuadTree = function(rect, maxDepth, currentDepth) { | |
this._rect = rect; | |
this._depth = currentDepth || 0; | |
this._maxDepth = maxDepth || 3; | |
this._hw = rect.width >> 1; | |
this._hh = rect.height >> 1; | |
this._midX = rect.x + this._hw; | |
this._midY = rect.y + this._hh; | |
this.clear(); | |
}; | |
QuadTree.prototype.clear = function() { | |
this._children = []; | |
this._topLeft = this._topRight = this._bottomLeft = this._bottomRight = null; | |
}; | |
QuadTree.prototype.toString = function() { | |
var s = "[QuadTreeNode> " | |
var l = this._children.length; | |
s += l > 1 ? (l + " children") : (l + " child") | |
return s; | |
}; | |
/** | |
* 节点按 <左上,右上,左下,右下>输出 | |
*/ | |
QuadTree.prototype.dump = function() { | |
var s = ""; | |
this.preorder(this, function(node){ | |
var d = node._depth; | |
for (var i = 0; i < d; i++) { | |
if (i == d - 1) | |
s += "+---"; | |
else | |
s += "| "; | |
} | |
s += node + "\n"; | |
}); | |
return s; | |
}; | |
QuadTree.prototype.preorder = function(node, process) { | |
process(node); | |
if (node._topLeft) { | |
this.preorder(node._topLeft, process); | |
} | |
if (node._topRight) { | |
this.preorder(node._topRight, process); | |
} | |
if (node._bottomLeft) { | |
this.preorder(node._bottomLeft, process); | |
} | |
if (node._bottomRight) { | |
this.preorder(node._bottomRight, process); | |
} | |
}; | |
// obj {rect:{x, y, width, height}} | |
QuadTree.prototype.retrieve = function(obj) { | |
var r = [] | |
if (!this._topLeft) { | |
r.push.apply(r, this._children) | |
return r; | |
} | |
var rect = obj.rect; | |
var rx = rect.x; | |
var ry = rect.y; | |
var rw = rect.width; | |
var rh = rect.height; | |
var rr = rx + rw; | |
var rb = ry + rh; | |
//如果所取区块比本身区域还大,那么它所有子树的children都取出 | |
if (rx <= this._rect.x && ry <= this._rect.y && rr >= this._rect.right && rb >= this._rect.bottom) { | |
r.push.apply(r, this._children) | |
r.push.apply(r, this._topLeft.retrieve(obj)) | |
r.push.apply(r, this._topRight.retrieve(obj)) | |
r.push.apply(r, this._bottomLeft.retrieve(obj)) | |
r.push.apply(r, this._bottomRight.retrieve(obj)) | |
return r; | |
} | |
//否则就只取对应的区域子树 | |
// 完全在分区里 | |
if ((rx > this._rect.x) && (rr < this._midX)) { | |
if (ry > this._rect.y && rb < this._midY) { | |
r.push.apply(r, this._topLeft.retrieve(obj)) | |
return r; | |
} | |
if (ry > this._midY && rb < this._rect.bottom) { | |
r.push.apply(r, this._bottomLeft.retrieve(obj)) | |
return r; | |
} | |
} | |
if (rx > this._midX && rr < this._rect.right) { | |
if (ry > this._rect.y && rb < this._midY) { | |
r.push.apply(r, this._topRight.retrieve(obj)) | |
return r | |
} | |
if (ry > this._midY && rb < this._rect.bottom) { | |
r.push.apply(r, this._bottomRight.retrieve(obj)) | |
return r | |
} | |
} | |
//只要有部分在分区里,也放到对应分区里,但注意可以重复放 | |
//上边 | |
if (rb > this._rect.y && ry < this._midY) { | |
if (rx < this._midX && rr > this._rect.x) { | |
r.push.apply(r, this._topLeft.retrieve(obj)); | |
} | |
if (rx < this._rect.right && rr > this._midX) { | |
r.push.apply(r, this._topRight.retrieve(obj)); | |
} | |
} | |
// 下边 | |
if (rb > this._midY && ry < this._rect.bottom) { | |
if (rx < this._midX && rr > this._rect.x) { | |
r.push.apply(r, this._bottomLeft.retrieve(obj)); | |
} | |
if (rx < this._rect.right && rr > this._midX) { | |
r.push.apply(r, this._bottomRight.retrieve(obj)); | |
} | |
} | |
return r; | |
}; | |
// obj {rect:{x, y, width, height}} or [obj, obj, obj] | |
QuadTree.prototype.insert = function(obj) { | |
if (Object.prototype.toString.call(obj) === "[object Array]") { | |
for (var i = 0, l = obj.length; i < l; i++) { | |
this.insert(obj[i]); | |
} | |
return; | |
} | |
var rect = obj.rect; | |
var rx = rect.x; | |
var ry = rect.y; | |
var rw = rect.width; | |
var rh = rect.height; | |
var rr = rx + rw; | |
var rb = ry + rh; | |
// 如果不能切分或者obj比整个区域还大,就放到children里 | |
if (this._depth >= this._maxDepth || (rx <= this._rect.x && ry <= this._rect.y && rx + rw >= this._rect.right && ry + rh >= this._rect.bottom)) { | |
this._children.push(obj); | |
return; | |
} | |
if (!this._topLeft) { | |
var d = this._depth + 1 | |
this._topLeft = new QuadTree(new Rectangle(this._rect.x, this._rect.y, this._hw, this._hh), this._maxDepth, d); | |
this._topRight = new QuadTree(new Rectangle(this._rect.x + this._hw, this._rect.y, this._hw, this._hh), this._maxDepth, d); | |
this._bottomLeft = new QuadTree(new Rectangle(this._rect.x, this._rect.y + this._hh, this._hw, this._hh), this._maxDepth, d); | |
this._bottomRight = new QuadTree(new Rectangle(this._rect.x + this._hw, this._rect.y + this._hh, this._hw, this._hh), this._maxDepth, d); | |
} | |
// 可以完全放到分区里就递归放到对应分区里 | |
if ((rx > this._rect.x) && (rr < this._midX)) { | |
if (ry > this._rect.y && rb < this._midY) { | |
this._topLeft.insert(obj); | |
return; | |
} | |
if (ry > this._midY && rb < this._rect.bottom) { | |
this._bottomLeft.insert(obj); | |
return; | |
} | |
} | |
if (rx > this._midX && rr < this._rect.right) { | |
if (ry > this._rect.y && rb < this._midY) { | |
this._topRight.insert(obj); | |
return; | |
} | |
if (ry > this._midY && rb < this._rect.bottom) { | |
this._bottomRight.insert(obj); | |
return; | |
} | |
} | |
//只要有部分在分区里,也放到对应分区里,但注意可以重复放 | |
//上边 | |
if (rb > this._rect.y && ry < this._midY) { | |
if (rx < this._midX && rr > this._rect.x) { | |
this._topLeft.insert(obj); | |
} | |
if (rx < this._rect.right && rr > this._midX) { | |
this._topRight.insert(obj); | |
} | |
} | |
// 下边 | |
if (rb > this._midY && ry < this._rect.bottom) { | |
if (rx < this._midX && rr > this._rect.x) { | |
this._bottomLeft.insert(obj); | |
} | |
if (rx < this._rect.right && rr > this._midX) { | |
this._bottomRight.insert(obj); | |
} | |
} | |
} | |
var width = 600; | |
var height = 600; | |
var canvas = document.querySelector("canvas"); | |
var ctx = canvas.getContext("2d"); | |
canvas.width = width; | |
canvas.height = height; | |
function Obj(x, y, width, height){ | |
this.x = x; | |
this.y = y; | |
this.width = width; | |
this.height = height; | |
this.isHit = false; | |
this.vx = Math.random()*1 - .5; | |
this.vy = Math.random()*1 - .5; | |
arr.push(this); | |
this.rect = new Rectangle(x, y, width, height); | |
}; | |
Obj.prototype.draw = function(){ | |
ctx.save(); | |
ctx.fillStyle = this.isHit?"red":"#f96"; | |
ctx.strokeStyle = "black"; | |
ctx.translate(this.x, this.y); | |
ctx.fillRect(0, 0, this.width, this.height); | |
ctx.strokeRect(0, 0, this.width, this.height); | |
ctx.restore(); | |
}; | |
Obj.prototype.hitTest = function(obj){ | |
return !(obj.x > this.width + this.x || obj.y > this.height + this.y || obj.x + obj.width < this.x || obj.y + obj.height < this.y); | |
}; | |
Obj.prototype.update = function(){ | |
this.rect.set(this.x, this.y, this.width, this.height); | |
this.x -= this.vx; | |
this.y -= this.vy; | |
if(this.x < 0){ | |
this.x = 0; | |
this.vx *= -1; | |
} | |
if(this.x > width - this.width){ | |
this.x = width - this.width; | |
this.vx *= -1; | |
} | |
if(this.y < 0){ | |
this.y = 0; | |
this.vy *= -1; | |
} | |
if(this.y > height - this.height){ | |
this.y = height - this.height; | |
this.vy *= -1; | |
} | |
this.isHit = false; | |
}; | |
var arr = []; | |
var quad = new QuadTree(new Rectangle(0, 0, width, height), 3, 0); | |
function update(){ | |
ctx.clearRect(0, 0, width, height); | |
quad.clear(); | |
for(var i = 0,l = arr.length;i < l;i ++){ | |
var obj = arr[i]; | |
obj.update(); | |
quad.insert(obj); | |
} | |
for(var i = 0,l = arr.length;i < l;i ++){ | |
var obj = arr[i]; | |
var quarArr = quad.retrieve(obj); | |
for(var j = 0, jl = quarArr.length;j < jl;j ++) | |
{ | |
var obj2 = quarArr[j]; | |
if(obj2 === obj)continue; | |
if(obj2.isHit && obj.isHit) continue | |
if(obj.hitTest(obj2)) | |
{ | |
obj.isHit = obj2.isHit = true; | |
} | |
} | |
} | |
for(var i = 0,l = arr.length;i < l;i ++){ | |
arr[i].draw(); | |
} | |
} | |
var interval = setInterval(update, 16); | |
function create(num){ | |
while(num --){ | |
new Obj(Math.random()*width, Math.random()*height, Math.random()*10+5, Math.random()*10+5) | |
} | |
} | |
create(1000); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment