diff --git a/examples/js.php b/examples/js.php index 7181313f..279877a8 100644 --- a/examples/js.php +++ b/examples/js.php @@ -68,6 +68,7 @@ + diff --git a/examples/quadtree.php b/examples/quadtree.php new file mode 100644 index 00000000..642a9a3c --- /dev/null +++ b/examples/quadtree.php @@ -0,0 +1,72 @@ + + + + phaser.js - a new beginning + + + + + + + + \ No newline at end of file diff --git a/src/core/Game.js b/src/core/Game.js index 3a8e168d..079abf84 100644 --- a/src/core/Game.js +++ b/src/core/Game.js @@ -376,19 +376,20 @@ Phaser.Game.prototype = { this.time.update(time); this.plugins.preUpdate(); + this.physics.preUpdate(); this.input.update(); this.tweens.update(); this.sound.update(); this.world.update(); this.state.update(); - // this.physics.update(); this.plugins.update(); this.renderer.render(this.world._stage); this.state.render(); this.world.postUpdate(); + this.physics.postUpdate(); this.plugins.postRender(); diff --git a/src/core/World.js b/src/core/World.js index 3b5a1c1d..cf251d59 100644 --- a/src/core/World.js +++ b/src/core/World.js @@ -53,7 +53,6 @@ Phaser.World.prototype = { var displayObject = this._stage; - // once the display object hits this. we can break the loop var testObject = displayObject.last._iNext; displayObject = displayObject.first; @@ -73,11 +72,8 @@ Phaser.World.prototype = { postUpdate: function () { - //this.camera.update(); - var displayObject = this._stage; - // once the display object hits this. we can break the loop var testObject = displayObject.last._iNext; displayObject = displayObject.first; diff --git a/src/math/LinkedList.js b/src/math/LinkedList.js deleted file mode 100644 index 95878518..00000000 --- a/src/math/LinkedList.js +++ /dev/null @@ -1,31 +0,0 @@ -/** -* Phaser - LinkedList -* -* A miniature linked list class. Useful for optimizing time-critical or highly repetitive tasks! -*/ - -/** -* Creates a new link, and sets object and next to null. -*/ -Phaser.LinkedList = function () { - - this.object = null; - this.next = null; - -}; - -Phaser.LinkedList.prototype = { - - destroy: function () { - - this.object = null; - - if (this.next != null) - { - this.next.destroy(); - } - - this.next = null; - } - -}; diff --git a/src/math/QuadTree.js b/src/math/QuadTree.js index 48ca23fa..a4626e71 100644 --- a/src/math/QuadTree.js +++ b/src/math/QuadTree.js @@ -1,34 +1,229 @@ -/** -* Phaser - QuadTree -* -* A fairly generic quad tree structure for rapid overlap checks. QuadTree is also configured for single or dual list operation. -* You can add items either to its A list or its B list. When you do an overlap check, you can compare the A list to itself, -* or the A list against the B list. Handy for different things! +/* + * Javascript QuadTree + * @version 1.0 + * @author Timo Hausmann + * + * Optimised to reduce temp. var creation and increase performance by Richard Davey + * + * Original version at https://github.com/timohausmann/quadtree-js/ + */ + +/* + Copyright © 2012 Timo Hausmann + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/** -* Instantiate a new Quad Tree node. - -* @extends Rectangle -* -* @param {Number} x The X-coordinate of the point in space. -* @param {Number} y The Y-coordinate of the point in space. -* @param {Number} width Desired width of this node. -* @param {Number} height Desired height of this node. -* @param {Number} parent The parent branch or node. Pass null to create a root. +/* +* QuadTree Constructor +* @param Integer maxObjects (optional) max objects a node can hold before splitting into 4 subnodes (default: 10) +* @param Integer maxLevels (optional) total max levels inside root QuadTree (default: 4) +* @param Integer level (optional) deepth level, required for subnodes */ -Phaser.QuadTree = function (manager, x, y, width, height, parent) { - - Phaser.Rectangle.call(this); +Phaser.QuadTree = function (x, y, width, height, maxObjects, maxLevels, level) { + + this.maxObjects = maxObjects || 10; + this.maxLevels = maxLevels || 4; + this.level = level || 0; + + this.bounds = { + x: x, + y: y, + width: width, + height: height, + subWidth: Math.floor(width / 2 ), + subHeight: Math.floor(height / 2 ), + right: x + Math.floor(width / 2 ), + bottom: y + Math.floor(height / 2 ) + }; + this.objects = []; + this.nodes = []; }; -// constructor -Phaser.QuadTree.prototype = Object.create(Phaser.Rectangle.prototype); -Phaser.QuadTree.prototype.constructor = Phaser.Rectangle; - Phaser.QuadTree.prototype = { + /* + * Split the node into 4 subnodes + */ + split: function() { + + // top right node + this.nodes[0] = new Phaser.QuadTree(this.bounds.right, this.bounds.y, this.bounds.subWidth, this.bounds.subHeight, this.maxObjects, this.maxLevels, this.level + 1); + + // top left node + this.nodes[1] = new Phaser.QuadTree(this.bounds.x, this.bounds.y, this.bounds.subWidth, this.bounds.subHeight, this.maxObjects, this.maxLevels, this.level + 1); + + // bottom left node + this.nodes[2] = new Phaser.QuadTree(this.bounds.x, this.bounds.bottom, this.bounds.subWidth, this.bounds.subHeight, this.maxObjects, this.maxLevels, this.level + 1); + + // bottom right node + this.nodes[3] = new Phaser.QuadTree(this.bounds.right, this.bounds.bottom, this.bounds.subWidth, this.bounds.subHeight, this.maxObjects, this.maxLevels, this.level + 1); -}; \ No newline at end of file + }, + + /* + * Determine which node the object belongs to + * @param Object pRect bounds of the area to be checked, with x, y, width, height + * @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 + */ + getIndex: function (rect) { + + var index = -1; + + // Duplicated comparisons, but they only get checked once in the flow so it saves creating temp. vars + + // rect can completely fit within the left quadrants + if (rect.x < this.bounds.bottom && rect.right < this.bounds.bottom) + { + if ((rect.y < this.bounds.right && rect.bottom < this.bounds.right)) + { + // rect can completely fit within the top quadrants + index = 1; + } + else if ((rect.y > this.bounds.right)) + { + // rect can completely fit within the bottom quadrants + index = 2; + } + } + else if (rect.x > this.bounds.bottom) + { + // rect can completely fit within the right quadrants + if ((rect.y < this.bounds.right && rect.bottom < this.bounds.right)) + { + index = 0; + } + else if ((rect.y > this.bounds.right)) + { + index = 3; + } + } + + return index; + + }, + + /* + * Insert the object into the node. If the node + * exceeds the capacity, it will split and add all + * objects to their corresponding subnodes. + * @param Object pRect bounds of the object to be added, with x, y, width, height + */ + insert: function (body) { + + var i = 0; + var index; + + // if we have subnodes ... + // if (typeof this.nodes[0] !== 'undefined') + if (this.nodes[0]) + { + index = this.getIndex(body.bounds); + + if (index !== -1) + { + this.nodes[index].insert(body); + return; + } + } + + this.objects.push(body); + + if (this.objects.length > this.maxObjects && this.level < this.maxLevels ) + { + // Split if we don't already have subnodes + // if (typeof this.nodes[0] === 'undefined') + if (!this.nodes[0]) + { + this.split(); + } + + // Add objects to subnodes + while (i < this.objects.length) + { + index = this.getIndex(this.objects[i].bounds); + + if (index !== -1 ) + { + // this is expensive - see what we can do about it + this.nodes[index].insert(this.objects.splice(i, 1)[0]); + } + else + { + i++; + } + } + } + }, + + /* + * Return all objects that could collide with the given object + * @param Object pRect bounds of the object to be checked, with x, y, width, height + * @Return Array array with all detected objects + */ + retrieve: function (rect) { + + var index = this.getIndex(rect); + var returnObjects = this.objects; + + // if we have subnodes ... + // if (typeof this.nodes[0] !== 'undefined') + if (this.nodes[0]) + { + // if rect fits into a subnode .. + if (index !== -1) + { + returnObjects = returnObjects.concat(this.nodes[index].retrieve(rect)); + } + else + { + // if rect does not fit into a subnode, check it against all subnodes + for (var i = 0, len = this.nodes.length; i < len; i++) + { + returnObjects = returnObjects.concat(this.nodes[i].retrieve(rect)); + } + } + } + + return returnObjects; + + }, + + /* + * Clear the quadtree + */ + clear: function () { + + this.objects = []; + + for (var i = 0, len = this.nodes.length; i < len; i++) + { + // if (typeof this.nodes[i] !== 'undefined') + if (this.nodes[i]) + { + this.nodes[i].clear(); + delete this.nodes[i]; + } + } + } + +}; diff --git a/src/physics/arcade/ArcadePhysics.js b/src/physics/arcade/ArcadePhysics.js index 40a1388d..47ddc246 100644 --- a/src/physics/arcade/ArcadePhysics.js +++ b/src/physics/arcade/ArcadePhysics.js @@ -8,9 +8,16 @@ Phaser.Physics.Arcade = function (game) { this.bounds = new Phaser.Rectangle(0, 0, game.world.width, game.world.height); /** + * Used by the QuadTree to set the maximum number of objects * @type {number} */ - this.worldDivisions = 6; + this.maxObjects = 10; + + /** + * Used by the QuadTree to set the maximum number of levels + * @type {number} + */ + this.maxLevels = 4; this.LEFT = 0x0001; this.RIGHT = 0x0010; @@ -25,6 +32,8 @@ Phaser.Physics.Arcade = function (game) { this.OVERLAP_BIAS = 4; this.TILE_OVERLAP = false; + this.quadTree = null; + // avoid gc spikes by caching these values for re-use this._obj1Bounds = new Phaser.Rectangle; this._obj2Bounds = new Phaser.Rectangle; @@ -126,6 +135,20 @@ Phaser.Physics.Arcade.prototype = { return velocity; + }, + + preUpdate: function () { + + // Create our tree which all of the Physics bodies will add themselves to + this.quadTree = new Phaser.QuadTree(this.game.world.bounds.x, this.game.world.bounds.y, this.game.world.bounds.width, this.game.world.bounds.height, this.maxObjects, this.maxLevels); + + }, + + postUpdate: function () { + + // Clear the tree ready for the next update + this.quadTree.clear(); + }, /** @@ -139,28 +162,6 @@ Phaser.Physics.Arcade.prototype = { */ overlap: function (object1, object2, notifyCallback, processCallback) { - if (object1 == null) - { - object1 = this._game.world.group; - } - - if (object2 == object1) - { - object2 = null; - } - - Phaser.QuadTree.divisions = this.worldDivisions; - - var quadTree = new Phaser.QuadTree(this._game.world.bounds.x, this._game.world.bounds.y, this._game.world.bounds.width, this._game.world.bounds.height); - - quadTree.load(object1, object2, notifyCallback, processCallback); - - var result = quadTree.execute(); - - quadTree.destroy(); - - quadTree = null; - return result; }, diff --git a/src/physics/arcade/Body.js b/src/physics/arcade/Body.js index 5dcfe577..5c0a8a11 100644 --- a/src/physics/arcade/Body.js +++ b/src/physics/arcade/Body.js @@ -58,6 +58,8 @@ Phaser.Physics.Arcade.Body = function (sprite) { this.allowRotation = false; this.allowGravity = true; + this.collideWorldBounds = false; + this.lastX = sprite.x; this.lastY = sprite.y; @@ -91,10 +93,50 @@ Phaser.Physics.Arcade.Body.prototype = { this.bounds.x = this.x; this.bounds.y = this.y; + if (this.allowCollisions & this.ANY) + { + this.game.physics.quadTree.insert(this); + } + + }, + + checkWorldBounds: function () { + + if (this.bounds.x < this.game.world.bounds.x) + { + this.x = this.game.world.bounds.x; + this.velocity.x *= -1; + this.velocity.x *= this.bounce.x; + } + else if (this.bounds.right > this.game.world.bounds.right) + { + this.x = this.game.world.bounds.right - this.width; + this.velocity.x *= -1; + this.velocity.x *= this.bounce.x; + } + + if (this.bounds.y < this.game.world.bounds.y) + { + this.y = this.game.world.bounds.y; + this.velocity.y *= -1; + this.velocity.y *= this.bounce.y; + } + else if (this.bounds.bottom > this.game.world.bounds.bottom) + { + this.y = this.game.world.bounds.bottom - this.height; + this.velocity.y *= -1; + this.velocity.y *= this.bounce.y; + } + }, postUpdate: function () { + if (this.collideWorldBounds) + { + this.checkWorldBounds(); + } + this.sprite.x = this.x - this.offset.x + (this.sprite.anchor.x * this.width); this.sprite.y = this.y - this.offset.y + (this.sprite.anchor.y * this.height); @@ -122,6 +164,7 @@ Phaser.Physics.Arcade.Body.prototype = { }, + /* hullWidth: function () { if (this.deltaX() > 0) @@ -173,6 +216,7 @@ Phaser.Physics.Arcade.Body.prototype = { } }, + */ deltaAbsX: function () { return (this.deltaX() > 0 ? this.deltaX() : -this.deltaX());