From d31777972cfe461e7928d476b0c89c80f8b1680e Mon Sep 17 00:00:00 2001 From: Richard Davey Date: Sun, 1 Sep 2013 19:52:50 +0100 Subject: [PATCH] Camera culling --- examples/camera_cull1.php | 72 +++++++++++++++++++++++++++ examples/get_bounds.php | 6 +-- src/core/Camera.js | 6 ++- src/core/World.js | 10 ++-- src/gameobjects/Sprite.js | 101 ++++++++++++++++++++++---------------- src/geom/Rectangle.js | 2 +- src/math/Math.js | 38 ++++++++++++++ src/utils/Debug.js | 18 +++---- 8 files changed, 188 insertions(+), 65 deletions(-) create mode 100644 examples/camera_cull1.php diff --git a/examples/camera_cull1.php b/examples/camera_cull1.php new file mode 100644 index 00000000..398753a7 --- /dev/null +++ b/examples/camera_cull1.php @@ -0,0 +1,72 @@ + + + + phaser.js - a new beginning + + + + + + + + \ No newline at end of file diff --git a/examples/get_bounds.php b/examples/get_bounds.php index 14ea1011..415eb9d9 100644 --- a/examples/get_bounds.php +++ b/examples/get_bounds.php @@ -15,7 +15,6 @@ var game = new Phaser.Game(800, 600, Phaser.CANVAS, '', { preload: preload, create: create, update: update, render: render }); var s; - var r; function preload() { game.load.image('test', 'assets/sprites/mana_card.png'); @@ -26,20 +25,17 @@ s = game.add.sprite(game.world.centerX, game.world.centerY, 'test'); s.anchor.setTo(0.5, 0.5); - r = s.getBounds(r); - } function update() { s.angle += 1; - s.getBounds(r); } function render() { - game.debug.renderRectangle(r); + game.debug.renderRectangle(s.bounds); } diff --git a/src/core/Camera.js b/src/core/Camera.js index 9f8134c3..17f85686 100644 --- a/src/core/Camera.js +++ b/src/core/Camera.js @@ -14,7 +14,9 @@ Phaser.Camera = function (game, id, x, y, width, height) { // The view into the world we wish to render (by default the game dimensions) // The x/y values are in world coordinates, not screen coordinates, the width/height is how many pixels to render // Objects outside of this view are not rendered (unless set to ignore the Camera, i.e. UI?) - this.view = new Phaser.Rectangle(x, y, width, height); + this.view = new Phaser.Rectangle(x, y, width, height); + this.screenView = new Phaser.Rectangle(x, y, width, height); + }; @@ -116,6 +118,8 @@ Phaser.Camera.prototype = { // this.plugins.preUpdate(); + // Add dirty flag + if (this.target !== null) { if (this.deadzone == null) diff --git a/src/core/World.js b/src/core/World.js index e0b170a4..a83fcd21 100644 --- a/src/core/World.js +++ b/src/core/World.js @@ -59,11 +59,11 @@ Phaser.World.prototype = { do { - if (!displayObject.visible) - { - displayObject = displayObject.last._iNext; - continue; - } + // if (!displayObject.active) + // { + // displayObject = displayObject.last._iNext; + // continue; + // } if (displayObject['update']) { diff --git a/src/gameobjects/Sprite.js b/src/gameobjects/Sprite.js index b4b4cb47..26e8a668 100644 --- a/src/gameobjects/Sprite.js +++ b/src/gameobjects/Sprite.js @@ -104,18 +104,15 @@ Phaser.Sprite = function (game, x, y, key, frame) { this.scrollFactor = new Phaser.Point(1, 1); - this.worldView = new Phaser.Rectangle(x, y, this.width, this.height); + // this.worldView = new Phaser.Rectangle(x, y, this.width, this.height); // Edge points this.offset = new Phaser.Point(); - this.center = new Phaser.Point(); this.topLeft = new Phaser.Point(); this.topRight = new Phaser.Point(); this.bottomRight = new Phaser.Point(); this.bottomLeft = new Phaser.Point(); - // Do we need all 4 edge points? It might be better to just calculate the center and apply the circle for a bounds check - this.getLocalPosition(this.center, this.offset.x + this.width / 2, this.offset.y + this.height / 2); this.getLocalPosition(this.topLeft, this.offset.x, this.offset.y); this.getLocalPosition(this.topRight, this.offset.x + this.width, this.offset.y); this.getLocalPosition(this.bottomLeft, this.offset.x, this.offset.y + this.height); @@ -133,6 +130,10 @@ Phaser.Sprite = function (game, x, y, key, frame) { this._a11 = 0; this._a12 = 0; this._id = 0; + this._left = null; + this._right = null; + this._top = null; + this._bottom = null; // The actual scale X value based on the worldTransform this._sx = 0; @@ -157,74 +158,90 @@ Phaser.Sprite.prototype.update = function() { this.animations.update(); - this.worldView.setTo(this._x, this._y, this.width, this.height); - - this.position.x = this._x - (this.game.world.camera.x * this.scrollFactor.x); - this.position.y = this._y - (this.game.world.camera.y * this.scrollFactor.y); - // |a c tx| // |b d ty| // |0 0 1| + this.position.x = this._x - (this.game.world.camera.x * this.scrollFactor.x); + this.position.y = this._y - (this.game.world.camera.y * this.scrollFactor.y); + + // Cache our transform values - if (this.worldTransform[0] != this._a00 || this.worldTransform[1] != this._a01) - { + // if (this.worldTransform[0] != this._a00 || this.worldTransform[1] != this._a01) + // { this._a00 = this.worldTransform[0]; // scaleX a this._a01 = this.worldTransform[1]; // skewY c this._sx = Math.sqrt((this._a00 * this._a00) + (this._a01 * this._a01)); this._a01 *= -1; this._dirty = true; - } + // } - if (this.worldTransform[3] != this._a10 || this.worldTransform[4] != this._a11) - { + // if (this.worldTransform[3] != this._a10 || this.worldTransform[4] != this._a11) + // { this._a10 = this.worldTransform[3]; // skewX b this._a11 = this.worldTransform[4]; // scaleY d this._sy = Math.sqrt((this._a10 * this._a10) + (this._a11 * this._a11)); this._a10 *= -1; this._dirty = true; - } + // } - if (this.worldTransform[2] != this._a02 || this.worldTransform[5] != this._a12) - { + // if (this.worldTransform[2] != this._a02 || this.worldTransform[5] != this._a12) + // { this._a02 = this.worldTransform[2]; // translateX tx this._a12 = this.worldTransform[5]; // translateY ty - this._dirty = true; - } + // this._a02 -= (this.game.world.camera.x * this.scrollFactor.x); + // this._a12 -= (this.game.world.camera.y * this.scrollFactor.y); +// this._dirty = true; +// } // If the frame has changed we ought to set _dirty this._sw = this.texture.frame.width * this._sx; this._sh = this.texture.frame.height * this._sy; - if (this._dirty) - { + //if (this._dirty) + //{ this._id = 1 / (this._a00 * this._a11 + this._a01 * -this._a10); - // Update the edge points - this.offset.setTo(this._a02 - (this.anchor.x * this._sw), this._a12 - (this.anchor.y * this._sh)); - - // Do we need all 4 edge points? It might be better to just calculate the center and apply the circle for a bounds check - this.getLocalPosition(this.center, this.offset.x + this.width / 2, this.offset.y + this.height / 2); - this.getLocalPosition(this.topLeft, this.offset.x, this.offset.y); - this.getLocalPosition(this.topRight, this.offset.x + this._sw, this.offset.y); - this.getLocalPosition(this.bottomLeft, this.offset.x, this.offset.y + this._sh); - this.getLocalPosition(this.bottomRight, this.offset.x + this._sw, this.offset.y + this._sh); - // Update our bounds - this.getBounds(this.bounds); - } + this.updateBounds(); + + this.visible = this.inCamera(this.game.world.camera.screenView); + //} // Check our bounds } +Phaser.Sprite.prototype.updateBounds = function() { + + // Update the edge points + this.offset.setTo(this._a02 - (this.anchor.x * this._sw), this._a12 - (this.anchor.y * this._sh)); + + this.getLocalPosition(this.topLeft, this.offset.x, this.offset.y); + this.getLocalPosition(this.topRight, this.offset.x + this._sw, this.offset.y); + this.getLocalPosition(this.bottomLeft, this.offset.x, this.offset.y + this._sh); + this.getLocalPosition(this.bottomRight, this.offset.x + this._sw, this.offset.y + this._sh); + + this._left = Phaser.Math.min(this.topLeft.x, this.topRight.x, this.bottomLeft.x, this.bottomRight.x); + this._right = Phaser.Math.max(this.topLeft.x, this.topRight.x, this.bottomLeft.x, this.bottomRight.x); + this._top = Phaser.Math.min(this.topLeft.y, this.topRight.y, this.bottomLeft.y, this.bottomRight.y); + this._bottom = Phaser.Math.max(this.topLeft.y, this.topRight.y, this.bottomLeft.y, this.bottomRight.y); + + this.bounds.setTo(this._left, this._top, this._right - this._left, this._bottom - this._top); + +} + +Phaser.Sprite.prototype.inCamera = function(cameraRect) { + + return Phaser.Rectangle.intersects(cameraRect, this.bounds, 0); + +} + Phaser.Sprite.prototype.getLocalPosition = function(p, x, y) { p.x = ((this._a11 * this._id * x + -this._a01 * this._id * y + (this._a12 * this._a01 - this._a02 * this._a11) * this._id) * this._sx) + this._a02; p.y = ((this._a00 * this._id * y + -this._a10 * this._id * x + (-this._a12 * this._a00 + this._a02 * this._a10) * this._id) * this._sy) + this._a12; - // At this point could be working out the smallest / largest value for the bounds check - return p; } @@ -233,17 +250,15 @@ Phaser.Sprite.prototype.getBounds = function(rect) { rect = rect || new Phaser.Rectangle; - var left = Math.min(this.topLeft.x, this.topRight.x, this.bottomLeft.x, this.bottomRight.x); - var right = Math.max(this.topLeft.x, this.topRight.x, this.bottomLeft.x, this.bottomRight.x); - var top = Math.min(this.topLeft.y, this.topRight.y, this.bottomLeft.y, this.bottomRight.y); - var bottom = Math.max(this.topLeft.y, this.topRight.y, this.bottomLeft.y, this.bottomRight.y); + var left = Phaser.Math.min(this.topLeft.x, this.topRight.x, this.bottomLeft.x, this.bottomRight.x); + var right = Phaser.Math.max(this.topLeft.x, this.topRight.x, this.bottomLeft.x, this.bottomRight.x); + var top = Phaser.Math.min(this.topLeft.y, this.topRight.y, this.bottomLeft.y, this.bottomRight.y); + var bottom = Phaser.Math.max(this.topLeft.y, this.topRight.y, this.bottomLeft.y, this.bottomRight.y); rect.x = left; rect.y = top; rect.width = right - left; rect.height = bottom - top; - - // This could work to include the children by running through the linked list and storing only the highest min.max values return rect; @@ -268,7 +283,7 @@ Object.defineProperty(Phaser.Sprite.prototype, 'x', { }, set: function(value) { - this.worldView.x = value; + // this.worldView.x = value; this._x = value; } @@ -281,7 +296,7 @@ Object.defineProperty(Phaser.Sprite.prototype, 'y', { }, set: function(value) { - this.worldView.y = value; + // this.worldView.y = value; this._y = value; } diff --git a/src/geom/Rectangle.js b/src/geom/Rectangle.js index cc452ffc..a6b0895c 100644 --- a/src/geom/Rectangle.js +++ b/src/geom/Rectangle.js @@ -663,7 +663,7 @@ Phaser.Rectangle.intersection = function (a, b, out) { */ Phaser.Rectangle.intersects = function (a, b, tolerance) { - if (typeof tolerance === "undefined") { tolerance = 0; } + tolerance = tolerance || 0; return !(a.left > b.right + tolerance || a.right < b.left - tolerance || a.top > b.bottom + tolerance || a.bottom < b.top - tolerance); diff --git a/src/math/Math.js b/src/math/Math.js index 924c8330..9aefc85e 100644 --- a/src/math/Math.js +++ b/src/math/Math.js @@ -428,6 +428,44 @@ Phaser.Math = { return true; } + }, + + /** + * Significantly faster version of Math.max + * See http://jsperf.com/math-s-min-max-vs-homemade/5 + * + * @return The highest value from those given. + */ + max: function () { + + for (var i = 1, max = 0, len = arguments.length; i < len; i++) + { + if (arguments[max] < arguments[i]) { + max = i; + } + } + + return arguments[max]; + + }, + + /** + * Significantly faster version of Math.min + * See http://jsperf.com/math-s-min-max-vs-homemade/5 + * + * @return The lowest value from those given. + */ + min: function () { + + for (var i =1 , min = 0, len = arguments.length; i < len; i++) + { + if (arguments[i] < arguments[min]){ + min = i; + } + } + + return arguments[min]; + }, /** diff --git a/src/utils/Debug.js b/src/utils/Debug.js index c1ec2c3a..6338e23b 100644 --- a/src/utils/Debug.js +++ b/src/utils/Debug.js @@ -273,6 +273,7 @@ Phaser.Utils.Debug.prototype = { this.line('Sprite: ' + ' (' + sprite.width + ' x ' + sprite.height + ') anchor: ' + sprite.anchor.x + ' x ' + sprite.anchor.y); this.line('x: ' + sprite.x.toFixed(1) + ' y: ' + sprite.y.toFixed(1) + ' rotation: ' + sprite.rotation.toFixed(1)); + this.line('rendering: ' + sprite.visible); // this.line('wx: ' + sprite.worldView.x + ' wy: ' + sprite.worldView.y + ' ww: ' + sprite.worldView.width.toFixed(1) + ' wh: ' + sprite.worldView.height.toFixed(1) + ' wb: ' + sprite.worldView.bottom + ' wr: ' + sprite.worldView.right); // this.line('sx: ' + sprite.scale.x.toFixed(1) + ' sy: ' + sprite.scale.y.toFixed(1)); @@ -284,17 +285,14 @@ Phaser.Utils.Debug.prototype = { // 5 = translateY - this.line('id: ' + sprite._id); - this.line('scale x: ' + sprite.worldTransform[0]); - this.line('scale y: ' + sprite.worldTransform[4]); - this.line('tx: ' + sprite.worldTransform[2]); - this.line('ty: ' + sprite.worldTransform[5]); - this.line('skew x: ' + sprite.worldTransform[3]); - this.line('skew y: ' + sprite.worldTransform[1]); + // this.line('id: ' + sprite._id); + // this.line('scale x: ' + sprite.worldTransform[0]); + // this.line('scale y: ' + sprite.worldTransform[4]); + // this.line('tx: ' + sprite.worldTransform[2]); + // this.line('ty: ' + sprite.worldTransform[5]); + // this.line('skew x: ' + sprite.worldTransform[3]); + // this.line('skew y: ' + sprite.worldTransform[1]); - // this.line('tx: ' + sprite.texture.width.toFixed(1) + ' ty: ' + sprite.texture.height.toFixed(1)); - // this.line('center x: ' + sprite.transform.center.x + ' y: ' + sprite.transform.center.y); - // this.line('cameraView x: ' + sprite.cameraView.x + ' y: ' + sprite.cameraView.y + ' width: ' + sprite.cameraView.width + ' height: ' + sprite.cameraView.height); // this.line('inCamera: ' + this.game.renderer.spriteRenderer.inCamera(this.game.camera, sprite)); },