Skip to content

Commit 128bdcc

Browse files
committed
Sorted out the QuadTree issues and resolved a small but vital bug in separateX, also re-organised the collision flags in Body.
1 parent d44261b commit 128bdcc

7 files changed

Lines changed: 265 additions & 121 deletions

File tree

examples/quadtree.php

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,14 @@ function preload() {
2525

2626
function create() {
2727

28+
game.world.setSize(2000, 2000);
29+
2830
aliens = [];
2931

30-
for (var i = 0; i < 100; i++)
32+
for (var i = 0; i < 1000; i++)
3133
{
3234
var s = game.add.sprite(game.world.randomX, game.world.randomY, 'baddie');
35+
s.name = 'alien' + s;
3336
s.body.collideWorldBounds = true;
3437
s.body.bounce.setTo(1, 1);
3538
s.body.velocity.setTo(10 + Math.random() * 10, 10 + Math.random() * 10);
@@ -51,6 +54,9 @@ function update() {
5154

5255
total = game.physics.quadTree.retrieve(ship);
5356

57+
// Get the ships top-most ID. If the length of that ID is 1 then we can ignore every other result,
58+
// it's simply not colliding with anything :)
59+
5460
for (var i = 0; i < total.length; i++)
5561
{
5662
total[i].sprite.alpha = 1;
@@ -80,12 +86,19 @@ function render() {
8086

8187
for (var i = 0; i < aliens.length; i++)
8288
{
83-
game.debug.renderRectangle(aliens[i].bounds);
89+
// game.debug.renderRectangle(aliens[i].bounds);
8490
}
8591

8692
game.debug.renderText(total.length, 32, 32);
8793
game.debug.renderQuadTree(game.physics.quadTree);
88-
game.debug.renderRectangle(ship);
94+
// game.debug.renderRectangle(ship);
95+
96+
game.debug.renderText('Index: ' + ship.body.quadTreeIndex, 32, 80);
97+
98+
for (var i = 0; i < ship.body.quadTreeIDs.length; i++)
99+
{
100+
game.debug.renderText('ID: ' + ship.body.quadTreeIDs[i], 32, 100 + (i * 20));
101+
}
89102

90103
}
91104

examples/quadtree2.php

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<!DOCTYPE HTML>
2+
<html>
3+
<head>
4+
<title>phaser.js - a new beginning</title>
5+
<?php
6+
require('js.php');
7+
?>
8+
</head>
9+
<body>
10+
11+
<script type="text/javascript">
12+
13+
(function () {
14+
15+
var game = new Phaser.Game(800, 600, Phaser.CANVAS, '', { preload: preload, create: create, update: update, render: render });
16+
17+
function preload() {
18+
game.load.image('ship', 'assets/sprites/xenon2_ship.png');
19+
game.load.image('baddie', 'assets/sprites/space-baddie.png');
20+
}
21+
22+
var ship;
23+
var total;
24+
var aliens;
25+
26+
function create() {
27+
28+
// game.world.setSize(2000, 2000);
29+
30+
aliens = [];
31+
32+
for (var i = 0; i < 10; i++)
33+
{
34+
var s = game.add.sprite(game.world.randomX, game.world.randomY, 'baddie');
35+
s.name = 'alien' + s;
36+
s.body.collideWorldBounds = true;
37+
s.body.bounce.setTo(1, 1);
38+
s.body.velocity.setTo(10 + Math.random() * 10, 10 + Math.random() * 10);
39+
aliens.push(s);
40+
}
41+
42+
ship = game.add.sprite(400, 400, 'ship');
43+
ship.body.collideWorldBounds = true;
44+
ship.body.bounce.setTo(0.5, 0.5);
45+
46+
}
47+
48+
function update() {
49+
50+
// total = game.physics.overlap(ship);
51+
52+
for (var i = 0; i < aliens.length; i++)
53+
{
54+
game.physics.separate(ship.body, aliens[i].body);
55+
}
56+
57+
if (game.input.keyboard.isDown(Phaser.Keyboard.LEFT))
58+
{
59+
ship.body.velocity.x -= 2;
60+
}
61+
else if (game.input.keyboard.isDown(Phaser.Keyboard.RIGHT))
62+
{
63+
ship.body.velocity.x += 2;
64+
}
65+
66+
if (game.input.keyboard.isDown(Phaser.Keyboard.UP))
67+
{
68+
ship.body.velocity.y -= 2;
69+
}
70+
else if (game.input.keyboard.isDown(Phaser.Keyboard.DOWN))
71+
{
72+
ship.body.velocity.y += 2;
73+
}
74+
75+
}
76+
77+
function render() {
78+
79+
game.debug.renderQuadTree(game.physics.quadTree);
80+
game.debug.renderRectangle(ship.body.bounds);
81+
82+
game.debug.renderText('up: ' + ship.body.touching.up, 32, 70);
83+
game.debug.renderText('down: ' + ship.body.touching.down, 32, 90);
84+
game.debug.renderText('left: ' + ship.body.touching.left, 32, 110);
85+
game.debug.renderText('right: ' + ship.body.touching.right, 32, 130);
86+
87+
}
88+
89+
})();
90+
91+
</script>
92+
93+
</body>
94+
</html>

src/geom/Rectangle.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -656,11 +656,15 @@ Phaser.Rectangle.containsPoint = function (a, point) {
656656
* @return {bool} A value of true if the Rectangle object contains the specified point; otherwise false.
657657
*/
658658
Phaser.Rectangle.containsRect = function (a, b) {
659+
659660
// If the given rect has a larger volume than this one then it can never contain it
660-
if(a.volume > b.volume) {
661+
if (a.volume > b.volume)
662+
{
661663
return false;
662664
}
665+
663666
return (a.x >= b.x && a.y >= b.y && a.right <= b.right && a.bottom <= b.bottom);
667+
664668
};
665669

666670
/**
@@ -685,9 +689,10 @@ Phaser.Rectangle.equals = function (a, b) {
685689
*/
686690
Phaser.Rectangle.intersection = function (a, b, out) {
687691

688-
if (typeof out === "undefined") { out = new Phaser.Rectangle(); }
692+
out = out || new Phaser.Rectangle;
689693

690-
if (Phaser.Rectangle.intersects(a, b)) {
694+
if (Phaser.Rectangle.intersects(a, b))
695+
{
691696
out.x = Math.max(a.x, b.x);
692697
out.y = Math.max(a.y, b.y);
693698
out.width = Math.min(a.right, b.right) - out.x;

src/math/QuadTree.js

Lines changed: 72 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
* @version 1.0
44
* @author Timo Hausmann
55
*
6-
* Optimised to reduce temp. var creation and increase performance by Richard Davey
6+
* @version 1.2, September 4th 2013
7+
* @author Richard Davey
8+
* The original code was a conversion of the Java code posted to GameDevTuts. However I've tweaked
9+
* it massively to add node indexing, removed lots of temp. var creation and significantly
10+
* increased performance as a result.
711
*
812
* Original version at https://github.com/timohausmann/quadtree-js/
913
*/
@@ -37,8 +41,12 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3741
* @param Integer maxLevels (optional) total max levels inside root QuadTree (default: 4)
3842
* @param Integer level (optional) deepth level, required for subnodes
3943
*/
40-
Phaser.QuadTree = function (x, y, width, height, maxObjects, maxLevels, level) {
44+
Phaser.QuadTree = function (physicsManager, x, y, width, height, maxObjects, maxLevels, level) {
4145

46+
this.physicsManager = physicsManager;
47+
this.ID = physicsManager.quadTreeID;
48+
physicsManager.quadTreeID++;
49+
4250
this.maxObjects = maxObjects || 10;
4351
this.maxLevels = maxLevels || 4;
4452
this.level = level || 0;
@@ -66,67 +74,19 @@ Phaser.QuadTree.prototype = {
6674
*/
6775
split: function() {
6876

69-
this.level + 1;
77+
this.level++;
7078

7179
// top right node
72-
this.nodes[0] = new Phaser.QuadTree(this.bounds.right, this.bounds.y, this.bounds.subWidth, this.bounds.subHeight, this.maxObjects, this.maxLevels, this.level);
80+
this.nodes[0] = new Phaser.QuadTree(this.physicsManager, this.bounds.right, this.bounds.y, this.bounds.subWidth, this.bounds.subHeight, this.maxObjects, this.maxLevels, this.level);
7381

7482
// top left node
75-
this.nodes[1] = new Phaser.QuadTree(this.bounds.x, this.bounds.y, this.bounds.subWidth, this.bounds.subHeight, this.maxObjects, this.maxLevels, this.level);
83+
this.nodes[1] = new Phaser.QuadTree(this.physicsManager, this.bounds.x, this.bounds.y, this.bounds.subWidth, this.bounds.subHeight, this.maxObjects, this.maxLevels, this.level);
7684

7785
// bottom left node
78-
this.nodes[2] = new Phaser.QuadTree(this.bounds.x, this.bounds.bottom, this.bounds.subWidth, this.bounds.subHeight, this.maxObjects, this.maxLevels, this.level);
86+
this.nodes[2] = new Phaser.QuadTree(this.physicsManager, this.bounds.x, this.bounds.bottom, this.bounds.subWidth, this.bounds.subHeight, this.maxObjects, this.maxLevels, this.level);
7987

8088
// bottom right node
81-
this.nodes[3] = new Phaser.QuadTree(this.bounds.right, this.bounds.bottom, this.bounds.subWidth, this.bounds.subHeight, this.maxObjects, this.maxLevels, this.level);
82-
83-
},
84-
85-
/*
86-
* Determine which node the object belongs to
87-
* @param Object pRect bounds of the area to be checked, with x, y, width, height
88-
* @return Integer index of the subnode (0-3), or -1 if pRect cannot completely fit within a subnode and is part of the parent node
89-
*/
90-
getIndex: function (rect) {
91-
92-
var index = -1;
93-
94-
// var verticalMidpoint = this.bounds.x + (this.bounds.width / 2);
95-
var verticalMidpoint = this.bounds.right;
96-
// var horizontalMidpoint = this.bounds.y + (this.bounds.height / 2);
97-
var horizontalMidpoint = this.bounds.bottom;
98-
99-
var topQuadrant = (rect.y < this.bounds.bottom && rect.bottom < this.bounds.bottom);
100-
var bottomQuadrant = (rect.y > this.bounds.bottom);
101-
102-
// rect can completely fit within the left quadrants
103-
if (rect.x < verticalMidpoint && rect.right < verticalMidpoint)
104-
{
105-
if (topQuadrant)
106-
{
107-
// rect can completely fit within the top quadrants
108-
index = 1;
109-
}
110-
else if (bottomQuadrant)
111-
{
112-
// rect can completely fit within the bottom quadrants
113-
index = 2;
114-
}
115-
}
116-
else if (rect.x > verticalMidpoint)
117-
{
118-
// rect can completely fit within the right quadrants
119-
if (topQuadrant)
120-
{
121-
index = 0;
122-
}
123-
else if (bottomQuadrant)
124-
{
125-
index = 3;
126-
}
127-
}
128-
129-
return index;
89+
this.nodes[3] = new Phaser.QuadTree(this.physicsManager, this.bounds.right, this.bounds.bottom, this.bounds.subWidth, this.bounds.subHeight, this.maxObjects, this.maxLevels, this.level);
13090

13191
},
13292

@@ -142,7 +102,6 @@ Phaser.QuadTree.prototype = {
142102
var index;
143103

144104
// if we have subnodes ...
145-
// if (typeof this.nodes[0] !== 'undefined')
146105
if (this.nodes[0] != null)
147106
{
148107
index = this.getIndex(body.bounds);
@@ -156,10 +115,9 @@ Phaser.QuadTree.prototype = {
156115

157116
this.objects.push(body);
158117

159-
if (this.objects.length > this.maxObjects && this.level < this.maxLevels )
118+
if (this.objects.length > this.maxObjects && this.level < this.maxLevels)
160119
{
161120
// Split if we don't already have subnodes
162-
// if (typeof this.nodes[0] === 'undefined')
163121
if (this.nodes[0] == null)
164122
{
165123
this.split();
@@ -170,7 +128,7 @@ Phaser.QuadTree.prototype = {
170128
{
171129
index = this.getIndex(this.objects[i].bounds);
172130

173-
if (index !== -1 )
131+
if (index !== -1)
174132
{
175133
// this is expensive - see what we can do about it
176134
this.nodes[index].insert(this.objects.splice(i, 1)[0]);
@@ -183,32 +141,76 @@ Phaser.QuadTree.prototype = {
183141
}
184142
},
185143

144+
/*
145+
* Determine which node the object belongs to
146+
* @param Object pRect bounds of the area to be checked, with x, y, width, height
147+
* @return Integer index of the subnode (0-3), or -1 if pRect cannot completely fit within a subnode and is part of the parent node
148+
*/
149+
getIndex: function (rect) {
150+
151+
// default is that rect doesn't fit, i.e. it straddles the internal quadrants
152+
var index = -1;
153+
154+
if (rect.x < this.bounds.right && rect.right < this.bounds.right)
155+
{
156+
if ((rect.y < this.bounds.bottom && rect.bottom < this.bounds.bottom))
157+
{
158+
// rect fits within the top-left quadrant of this quadtree
159+
index = 1;
160+
}
161+
else if ((rect.y > this.bounds.bottom))
162+
{
163+
// rect fits within the bottom-left quadrant of this quadtree
164+
index = 2;
165+
}
166+
}
167+
else if (rect.x > this.bounds.right)
168+
{
169+
// rect can completely fit within the right quadrants
170+
if ((rect.y < this.bounds.bottom && rect.bottom < this.bounds.bottom))
171+
{
172+
// rect fits within the top-right quadrant of this quadtree
173+
index = 0;
174+
}
175+
else if ((rect.y > this.bounds.bottom))
176+
{
177+
// rect fits within the bottom-right quadrant of this quadtree
178+
index = 3;
179+
}
180+
}
181+
182+
return index;
183+
184+
},
185+
186186
/*
187187
* Return all objects that could collide with the given object
188188
* @param Object pRect bounds of the object to be checked, with x, y, width, height
189189
* @Return Array array with all detected objects
190190
*/
191191
retrieve: function (sprite) {
192192

193-
var index = this.getIndex(sprite.body.bounds);
194193
var returnObjects = this.objects;
195-
196-
// if we have subnodes ...
197-
// if (typeof this.nodes[0] !== 'undefined')
194+
195+
sprite.body.quadTreeIndex = this.getIndex(sprite.body.bounds);
196+
197+
// Temp store for the node IDs this sprite is in, we can use this for fast elimination later
198+
sprite.body.quadTreeIDs.push(this.ID);
199+
198200
if (this.nodes[0])
199201
{
200202
// if rect fits into a subnode ..
201-
if (index !== -1)
203+
if (sprite.body.quadTreeIndex !== -1)
202204
{
203-
returnObjects = returnObjects.concat(this.nodes[index].retrieve(sprite));
205+
returnObjects = returnObjects.concat(this.nodes[sprite.body.quadTreeIndex].retrieve(sprite));
204206
}
205207
else
206208
{
207-
// if rect does not fit into a subnode, check it against all subnodes
208-
for (var i = 0, len = this.nodes.length; i < len; i++)
209-
{
210-
returnObjects = returnObjects.concat(this.nodes[i].retrieve(sprite));
211-
}
209+
// if rect does not fit into a subnode, check it against all subnodes (unrolled for speed)
210+
returnObjects = returnObjects.concat(this.nodes[0].retrieve(sprite));
211+
returnObjects = returnObjects.concat(this.nodes[1].retrieve(sprite));
212+
returnObjects = returnObjects.concat(this.nodes[2].retrieve(sprite));
213+
returnObjects = returnObjects.concat(this.nodes[3].retrieve(sprite));
212214
}
213215
}
214216

0 commit comments

Comments
 (0)