diff --git a/src/core/display/BoundsBuilder.js b/src/core/display/BoundsBuilder.js new file mode 100644 index 0000000..f86d208 --- /dev/null +++ b/src/core/display/BoundsBuilder.js @@ -0,0 +1,216 @@ +var math = require('../math'), + Rectangle = math.Rectangle; + +/** + * 'Builder' pattern for bounds rectangles + * Axis-Aligned Bounding Box + * It is not a shape! Its mutable thing, no 'EMPTY' or that kind of problems + * + * @class + * @memberof PIXI + */ +function BoundsBuilder() +{ + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; + + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; + + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; + + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; +} + +BoundsBuilder.prototype.constructor = BoundsBuilder; +module.exports = BoundsBuilder; + +BoundsBuilder.prototype.isEmpty = function() +{ + return this.minX > this.maxX || this.minY > this.maxY; +}; + +BoundsBuilder.prototype.clear = function() +{ + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; +}; + +/** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ +BoundsBuilder.prototype.getRectangle = function(tempRect) +{ + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + tempRect = tempRect || new Rectangle(0, 0, 1, 1); + tempRect.x = this.minX; + tempRect.y = this.minY; + tempRect.width = this.maxX - this.minX; + tempRect.height = this.maxY - this.minY; + return tempRect; +}; + +/** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ +BoundsBuilder.prototype.addPoint = function (point) +{ + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); +}; + +/** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.BoundsBuilder} + */ +BoundsBuilder.prototype.addQuad = function(vertices) +{ + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +/** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ +BoundsBuilder.prototype.addFrame = function(transform, x0, y0, x1, y1) +{ + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +/** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ +BoundsBuilder.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) +{ + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +BoundsBuilder.prototype.addBounds = function(bounds) +{ + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; +}; diff --git a/src/core/display/BoundsBuilder.js b/src/core/display/BoundsBuilder.js new file mode 100644 index 0000000..f86d208 --- /dev/null +++ b/src/core/display/BoundsBuilder.js @@ -0,0 +1,216 @@ +var math = require('../math'), + Rectangle = math.Rectangle; + +/** + * 'Builder' pattern for bounds rectangles + * Axis-Aligned Bounding Box + * It is not a shape! Its mutable thing, no 'EMPTY' or that kind of problems + * + * @class + * @memberof PIXI + */ +function BoundsBuilder() +{ + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; + + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; + + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; + + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; +} + +BoundsBuilder.prototype.constructor = BoundsBuilder; +module.exports = BoundsBuilder; + +BoundsBuilder.prototype.isEmpty = function() +{ + return this.minX > this.maxX || this.minY > this.maxY; +}; + +BoundsBuilder.prototype.clear = function() +{ + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; +}; + +/** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ +BoundsBuilder.prototype.getRectangle = function(tempRect) +{ + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + tempRect = tempRect || new Rectangle(0, 0, 1, 1); + tempRect.x = this.minX; + tempRect.y = this.minY; + tempRect.width = this.maxX - this.minX; + tempRect.height = this.maxY - this.minY; + return tempRect; +}; + +/** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ +BoundsBuilder.prototype.addPoint = function (point) +{ + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); +}; + +/** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.BoundsBuilder} + */ +BoundsBuilder.prototype.addQuad = function(vertices) +{ + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +/** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ +BoundsBuilder.prototype.addFrame = function(transform, x0, y0, x1, y1) +{ + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +/** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ +BoundsBuilder.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) +{ + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +BoundsBuilder.prototype.addBounds = function(bounds) +{ + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; +}; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 7992d77..e69807a 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -1,5 +1,4 @@ -var math = require('../math'), - utils = require('../utils'), +var utils = require('../utils'), DisplayObject = require('./DisplayObject'); /** @@ -364,6 +363,8 @@ */ Container.prototype.updateTransform = function () { + this._currentBounds = null; + if (!this.visible) { return; @@ -379,111 +380,39 @@ this.children[i].updateTransform(); } - this._currentBounds = null; + }; // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; -/** -* Retrieves the bounds of the Container as a rectangle. The bounds calculation takes all visible children into consideration. - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ -Container.prototype.getBounds = function () + +Container.prototype.calculateBounds = function () { - if(!this._currentBounds) + this._bounds_.clear(); + // if we have already done this on THIS frame. + + if(!this.visible) { - - if (this.children.length === 0) - { - return math.Rectangle.EMPTY; - } - - // TODO the bounds have already been calculated this render session so return what we have - - var minX = Infinity; - var minY = Infinity; - - var maxX = -Infinity; - var maxY = -Infinity; - - var childBounds; - var childMaxX; - var childMaxY; - - var childVisible = false; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - childBounds = this.children[i].getBounds(); - if (childBounds === math.Rectangle.EMPTY) { - continue; - } - childVisible = true; - - minX = minX < childBounds.x ? minX : childBounds.x; - minY = minY < childBounds.y ? minY : childBounds.y; - - childMaxX = childBounds.width + childBounds.x; - childMaxY = childBounds.height + childBounds.y; - - maxX = maxX > childMaxX ? maxX : childMaxX; - maxY = maxY > childMaxY ? maxY : childMaxY; - } - - if (!childVisible) - { - this._currentBounds = math.Rectangle.EMPTY; - return this._currentBounds; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.y = minY; - bounds.width = maxX - minX; - bounds.height = maxY - minY; - - this._currentBounds = bounds; + return; } - return this._currentBounds; + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds_.addBounds(child._bounds_); + } }; -Container.prototype.containerGetBounds = Container.prototype.getBounds; - -/** - * Retrieves the non-global local bounds of the Container as a rectangle. - * The calculation takes all visible children into consideration. - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ -Container.prototype.getLocalBounds = function () +Container.prototype._calculateBounds = function () { - var matrixCache = this.transform.worldTransform; - - this.transform.worldTransform = math.Matrix.IDENTITY; - this.transform._worldID++; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } - - this.transform.worldTransform = matrixCache; - this.transform._worldID++; - - this._currentBounds = null; - - return this.getBounds(); + //FILL IN// }; /** diff --git a/src/core/display/BoundsBuilder.js b/src/core/display/BoundsBuilder.js new file mode 100644 index 0000000..f86d208 --- /dev/null +++ b/src/core/display/BoundsBuilder.js @@ -0,0 +1,216 @@ +var math = require('../math'), + Rectangle = math.Rectangle; + +/** + * 'Builder' pattern for bounds rectangles + * Axis-Aligned Bounding Box + * It is not a shape! Its mutable thing, no 'EMPTY' or that kind of problems + * + * @class + * @memberof PIXI + */ +function BoundsBuilder() +{ + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; + + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; + + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; + + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; +} + +BoundsBuilder.prototype.constructor = BoundsBuilder; +module.exports = BoundsBuilder; + +BoundsBuilder.prototype.isEmpty = function() +{ + return this.minX > this.maxX || this.minY > this.maxY; +}; + +BoundsBuilder.prototype.clear = function() +{ + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; +}; + +/** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ +BoundsBuilder.prototype.getRectangle = function(tempRect) +{ + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + tempRect = tempRect || new Rectangle(0, 0, 1, 1); + tempRect.x = this.minX; + tempRect.y = this.minY; + tempRect.width = this.maxX - this.minX; + tempRect.height = this.maxY - this.minY; + return tempRect; +}; + +/** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ +BoundsBuilder.prototype.addPoint = function (point) +{ + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); +}; + +/** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.BoundsBuilder} + */ +BoundsBuilder.prototype.addQuad = function(vertices) +{ + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +/** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ +BoundsBuilder.prototype.addFrame = function(transform, x0, y0, x1, y1) +{ + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +/** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ +BoundsBuilder.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) +{ + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +BoundsBuilder.prototype.addBounds = function(bounds) +{ + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; +}; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 7992d77..e69807a 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -1,5 +1,4 @@ -var math = require('../math'), - utils = require('../utils'), +var utils = require('../utils'), DisplayObject = require('./DisplayObject'); /** @@ -364,6 +363,8 @@ */ Container.prototype.updateTransform = function () { + this._currentBounds = null; + if (!this.visible) { return; @@ -379,111 +380,39 @@ this.children[i].updateTransform(); } - this._currentBounds = null; + }; // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; -/** -* Retrieves the bounds of the Container as a rectangle. The bounds calculation takes all visible children into consideration. - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ -Container.prototype.getBounds = function () + +Container.prototype.calculateBounds = function () { - if(!this._currentBounds) + this._bounds_.clear(); + // if we have already done this on THIS frame. + + if(!this.visible) { - - if (this.children.length === 0) - { - return math.Rectangle.EMPTY; - } - - // TODO the bounds have already been calculated this render session so return what we have - - var minX = Infinity; - var minY = Infinity; - - var maxX = -Infinity; - var maxY = -Infinity; - - var childBounds; - var childMaxX; - var childMaxY; - - var childVisible = false; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - childBounds = this.children[i].getBounds(); - if (childBounds === math.Rectangle.EMPTY) { - continue; - } - childVisible = true; - - minX = minX < childBounds.x ? minX : childBounds.x; - minY = minY < childBounds.y ? minY : childBounds.y; - - childMaxX = childBounds.width + childBounds.x; - childMaxY = childBounds.height + childBounds.y; - - maxX = maxX > childMaxX ? maxX : childMaxX; - maxY = maxY > childMaxY ? maxY : childMaxY; - } - - if (!childVisible) - { - this._currentBounds = math.Rectangle.EMPTY; - return this._currentBounds; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.y = minY; - bounds.width = maxX - minX; - bounds.height = maxY - minY; - - this._currentBounds = bounds; + return; } - return this._currentBounds; + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds_.addBounds(child._bounds_); + } }; -Container.prototype.containerGetBounds = Container.prototype.getBounds; - -/** - * Retrieves the non-global local bounds of the Container as a rectangle. - * The calculation takes all visible children into consideration. - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ -Container.prototype.getLocalBounds = function () +Container.prototype._calculateBounds = function () { - var matrixCache = this.transform.worldTransform; - - this.transform.worldTransform = math.Matrix.IDENTITY; - this.transform._worldID++; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } - - this.transform.worldTransform = matrixCache; - this.transform._worldID++; - - this._currentBounds = null; - - return this.getBounds(); + //FILL IN// }; /** diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index ff4fda1..535f833 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,9 +3,9 @@ CONST = require('../const'), TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), + BoundsBulder = require('./BoundsBuilder'), _tempDisplayObjectParent = new DisplayObject(); - /** * The base class for all objects that are rendered on the screen. * This is an abstract class and should not be used on its own rather it should be extended. @@ -102,6 +102,8 @@ * @private */ this._mask = null; + + this._bounds_ = new BoundsBulder(); } // constructor @@ -350,15 +352,53 @@ DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; /** + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() + */ +DisplayObject.prototype._recursivePostUpdateTransform = function() +{ + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(_tempDisplayObjectParent.transform); + } +}; + +/** * * * Retrieves the bounds of the displayObject as a rectangle object * * @return {PIXI.Rectangle} the rectangular bounding area */ -DisplayObject.prototype.getBounds = function () // jshint unused:false +DisplayObject.prototype.getBounds = function (skipUpdate) { - return math.Rectangle.EMPTY; + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(!this._currentBounds) + { + this.calculateBounds(); + this._currentBounds = this._bounds_.getRectangle(this._bounds); + } + + return this._currentBounds; }; /** @@ -368,7 +408,18 @@ */ DisplayObject.prototype.getLocalBounds = function () { - return this.getBounds(math.Matrix.IDENTITY); + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = _tempDisplayObjectParent.transform; + + var bounds = this.getBounds(); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; }; /** @@ -377,24 +428,29 @@ * @param position {PIXI.Point} The world origin to calculate from * @return {PIXI.Point} A point object representing the position of this object */ -DisplayObject.prototype.toGlobal = function (position) +DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) { - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) + if(!skipUpdate) { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } } // don't need to update the lot - return this.worldTransform.apply(position); + return this.worldTransform.apply(position, point); }; /** @@ -405,25 +461,30 @@ * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) * @return {PIXI.Point} A point object representing the position of this object */ -DisplayObject.prototype.toLocal = function (position, from, point) +DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) { if (from) { - position = from.toGlobal(position); + position = from.toGlobal(position, point, skipUpdate); } - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) + if(! skipUpdate) { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } } // simply apply the matrix.. diff --git a/src/core/display/BoundsBuilder.js b/src/core/display/BoundsBuilder.js new file mode 100644 index 0000000..f86d208 --- /dev/null +++ b/src/core/display/BoundsBuilder.js @@ -0,0 +1,216 @@ +var math = require('../math'), + Rectangle = math.Rectangle; + +/** + * 'Builder' pattern for bounds rectangles + * Axis-Aligned Bounding Box + * It is not a shape! Its mutable thing, no 'EMPTY' or that kind of problems + * + * @class + * @memberof PIXI + */ +function BoundsBuilder() +{ + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; + + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; + + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; + + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; +} + +BoundsBuilder.prototype.constructor = BoundsBuilder; +module.exports = BoundsBuilder; + +BoundsBuilder.prototype.isEmpty = function() +{ + return this.minX > this.maxX || this.minY > this.maxY; +}; + +BoundsBuilder.prototype.clear = function() +{ + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; +}; + +/** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ +BoundsBuilder.prototype.getRectangle = function(tempRect) +{ + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + tempRect = tempRect || new Rectangle(0, 0, 1, 1); + tempRect.x = this.minX; + tempRect.y = this.minY; + tempRect.width = this.maxX - this.minX; + tempRect.height = this.maxY - this.minY; + return tempRect; +}; + +/** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ +BoundsBuilder.prototype.addPoint = function (point) +{ + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); +}; + +/** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.BoundsBuilder} + */ +BoundsBuilder.prototype.addQuad = function(vertices) +{ + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +/** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ +BoundsBuilder.prototype.addFrame = function(transform, x0, y0, x1, y1) +{ + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +/** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ +BoundsBuilder.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) +{ + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +BoundsBuilder.prototype.addBounds = function(bounds) +{ + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; +}; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 7992d77..e69807a 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -1,5 +1,4 @@ -var math = require('../math'), - utils = require('../utils'), +var utils = require('../utils'), DisplayObject = require('./DisplayObject'); /** @@ -364,6 +363,8 @@ */ Container.prototype.updateTransform = function () { + this._currentBounds = null; + if (!this.visible) { return; @@ -379,111 +380,39 @@ this.children[i].updateTransform(); } - this._currentBounds = null; + }; // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; -/** -* Retrieves the bounds of the Container as a rectangle. The bounds calculation takes all visible children into consideration. - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ -Container.prototype.getBounds = function () + +Container.prototype.calculateBounds = function () { - if(!this._currentBounds) + this._bounds_.clear(); + // if we have already done this on THIS frame. + + if(!this.visible) { - - if (this.children.length === 0) - { - return math.Rectangle.EMPTY; - } - - // TODO the bounds have already been calculated this render session so return what we have - - var minX = Infinity; - var minY = Infinity; - - var maxX = -Infinity; - var maxY = -Infinity; - - var childBounds; - var childMaxX; - var childMaxY; - - var childVisible = false; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - childBounds = this.children[i].getBounds(); - if (childBounds === math.Rectangle.EMPTY) { - continue; - } - childVisible = true; - - minX = minX < childBounds.x ? minX : childBounds.x; - minY = minY < childBounds.y ? minY : childBounds.y; - - childMaxX = childBounds.width + childBounds.x; - childMaxY = childBounds.height + childBounds.y; - - maxX = maxX > childMaxX ? maxX : childMaxX; - maxY = maxY > childMaxY ? maxY : childMaxY; - } - - if (!childVisible) - { - this._currentBounds = math.Rectangle.EMPTY; - return this._currentBounds; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.y = minY; - bounds.width = maxX - minX; - bounds.height = maxY - minY; - - this._currentBounds = bounds; + return; } - return this._currentBounds; + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds_.addBounds(child._bounds_); + } }; -Container.prototype.containerGetBounds = Container.prototype.getBounds; - -/** - * Retrieves the non-global local bounds of the Container as a rectangle. - * The calculation takes all visible children into consideration. - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ -Container.prototype.getLocalBounds = function () +Container.prototype._calculateBounds = function () { - var matrixCache = this.transform.worldTransform; - - this.transform.worldTransform = math.Matrix.IDENTITY; - this.transform._worldID++; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } - - this.transform.worldTransform = matrixCache; - this.transform._worldID++; - - this._currentBounds = null; - - return this.getBounds(); + //FILL IN// }; /** diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index ff4fda1..535f833 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,9 +3,9 @@ CONST = require('../const'), TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), + BoundsBulder = require('./BoundsBuilder'), _tempDisplayObjectParent = new DisplayObject(); - /** * The base class for all objects that are rendered on the screen. * This is an abstract class and should not be used on its own rather it should be extended. @@ -102,6 +102,8 @@ * @private */ this._mask = null; + + this._bounds_ = new BoundsBulder(); } // constructor @@ -350,15 +352,53 @@ DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; /** + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() + */ +DisplayObject.prototype._recursivePostUpdateTransform = function() +{ + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(_tempDisplayObjectParent.transform); + } +}; + +/** * * * Retrieves the bounds of the displayObject as a rectangle object * * @return {PIXI.Rectangle} the rectangular bounding area */ -DisplayObject.prototype.getBounds = function () // jshint unused:false +DisplayObject.prototype.getBounds = function (skipUpdate) { - return math.Rectangle.EMPTY; + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(!this._currentBounds) + { + this.calculateBounds(); + this._currentBounds = this._bounds_.getRectangle(this._bounds); + } + + return this._currentBounds; }; /** @@ -368,7 +408,18 @@ */ DisplayObject.prototype.getLocalBounds = function () { - return this.getBounds(math.Matrix.IDENTITY); + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = _tempDisplayObjectParent.transform; + + var bounds = this.getBounds(); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; }; /** @@ -377,24 +428,29 @@ * @param position {PIXI.Point} The world origin to calculate from * @return {PIXI.Point} A point object representing the position of this object */ -DisplayObject.prototype.toGlobal = function (position) +DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) { - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) + if(!skipUpdate) { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } } // don't need to update the lot - return this.worldTransform.apply(position); + return this.worldTransform.apply(position, point); }; /** @@ -405,25 +461,30 @@ * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) * @return {PIXI.Point} A point object representing the position of this object */ -DisplayObject.prototype.toLocal = function (position, from, point) +DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) { if (from) { - position = from.toGlobal(position); + position = from.toGlobal(position, point, skipUpdate); } - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) + if(! skipUpdate) { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } } // simply apply the matrix.. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 6c575d8..4e37d2d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -5,6 +5,7 @@ Sprite = require('../sprites/Sprite'), math = require('../math'), CONST = require('../const'), + BoundsBuilder = require('../display/BoundsBuilder'), bezierCurveTo = require('./utils/bezierCurveTo'), CanvasRenderer = require('../renderers/canvas/CanvasRenderer'), canvasRenderer, @@ -118,7 +119,7 @@ * @member {PIXI.Rectangle} * @private */ - this._localBounds = new math.Rectangle(0,0,1,1); + this._localBounds = new BoundsBuilder(); /** * Used to detect if the graphics object has changed. If this is set to true then the graphics @@ -774,87 +775,24 @@ * object's worldTransform. * @return {PIXI.Rectangle} the rectangular bounding area */ -Graphics.prototype.getBounds = function (matrix) +Graphics.prototype._calculateBounds = function () { - if(!this._currentBounds) + if (!this.renderable) { - - // return an empty object if the item is a mask! - if (!this.renderable) - { - return math.Rectangle.EMPTY; - } - - if (this.boundsDirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.boundsDirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - this._currentBounds = this._bounds; + return; } - return this._currentBounds; + if (this.boundsDirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.boundsDirty = false; + } + + var lb = this._localBounds; + this._bounds_.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); }; /** @@ -982,11 +920,11 @@ var padding = this.boundsPadding; - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; }; diff --git a/src/core/display/BoundsBuilder.js b/src/core/display/BoundsBuilder.js new file mode 100644 index 0000000..f86d208 --- /dev/null +++ b/src/core/display/BoundsBuilder.js @@ -0,0 +1,216 @@ +var math = require('../math'), + Rectangle = math.Rectangle; + +/** + * 'Builder' pattern for bounds rectangles + * Axis-Aligned Bounding Box + * It is not a shape! Its mutable thing, no 'EMPTY' or that kind of problems + * + * @class + * @memberof PIXI + */ +function BoundsBuilder() +{ + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; + + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; + + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; + + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; +} + +BoundsBuilder.prototype.constructor = BoundsBuilder; +module.exports = BoundsBuilder; + +BoundsBuilder.prototype.isEmpty = function() +{ + return this.minX > this.maxX || this.minY > this.maxY; +}; + +BoundsBuilder.prototype.clear = function() +{ + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; +}; + +/** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ +BoundsBuilder.prototype.getRectangle = function(tempRect) +{ + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + tempRect = tempRect || new Rectangle(0, 0, 1, 1); + tempRect.x = this.minX; + tempRect.y = this.minY; + tempRect.width = this.maxX - this.minX; + tempRect.height = this.maxY - this.minY; + return tempRect; +}; + +/** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ +BoundsBuilder.prototype.addPoint = function (point) +{ + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); +}; + +/** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.BoundsBuilder} + */ +BoundsBuilder.prototype.addQuad = function(vertices) +{ + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +/** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ +BoundsBuilder.prototype.addFrame = function(transform, x0, y0, x1, y1) +{ + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +/** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ +BoundsBuilder.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) +{ + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +BoundsBuilder.prototype.addBounds = function(bounds) +{ + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; +}; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 7992d77..e69807a 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -1,5 +1,4 @@ -var math = require('../math'), - utils = require('../utils'), +var utils = require('../utils'), DisplayObject = require('./DisplayObject'); /** @@ -364,6 +363,8 @@ */ Container.prototype.updateTransform = function () { + this._currentBounds = null; + if (!this.visible) { return; @@ -379,111 +380,39 @@ this.children[i].updateTransform(); } - this._currentBounds = null; + }; // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; -/** -* Retrieves the bounds of the Container as a rectangle. The bounds calculation takes all visible children into consideration. - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ -Container.prototype.getBounds = function () + +Container.prototype.calculateBounds = function () { - if(!this._currentBounds) + this._bounds_.clear(); + // if we have already done this on THIS frame. + + if(!this.visible) { - - if (this.children.length === 0) - { - return math.Rectangle.EMPTY; - } - - // TODO the bounds have already been calculated this render session so return what we have - - var minX = Infinity; - var minY = Infinity; - - var maxX = -Infinity; - var maxY = -Infinity; - - var childBounds; - var childMaxX; - var childMaxY; - - var childVisible = false; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - childBounds = this.children[i].getBounds(); - if (childBounds === math.Rectangle.EMPTY) { - continue; - } - childVisible = true; - - minX = minX < childBounds.x ? minX : childBounds.x; - minY = minY < childBounds.y ? minY : childBounds.y; - - childMaxX = childBounds.width + childBounds.x; - childMaxY = childBounds.height + childBounds.y; - - maxX = maxX > childMaxX ? maxX : childMaxX; - maxY = maxY > childMaxY ? maxY : childMaxY; - } - - if (!childVisible) - { - this._currentBounds = math.Rectangle.EMPTY; - return this._currentBounds; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.y = minY; - bounds.width = maxX - minX; - bounds.height = maxY - minY; - - this._currentBounds = bounds; + return; } - return this._currentBounds; + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds_.addBounds(child._bounds_); + } }; -Container.prototype.containerGetBounds = Container.prototype.getBounds; - -/** - * Retrieves the non-global local bounds of the Container as a rectangle. - * The calculation takes all visible children into consideration. - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ -Container.prototype.getLocalBounds = function () +Container.prototype._calculateBounds = function () { - var matrixCache = this.transform.worldTransform; - - this.transform.worldTransform = math.Matrix.IDENTITY; - this.transform._worldID++; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } - - this.transform.worldTransform = matrixCache; - this.transform._worldID++; - - this._currentBounds = null; - - return this.getBounds(); + //FILL IN// }; /** diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index ff4fda1..535f833 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,9 +3,9 @@ CONST = require('../const'), TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), + BoundsBulder = require('./BoundsBuilder'), _tempDisplayObjectParent = new DisplayObject(); - /** * The base class for all objects that are rendered on the screen. * This is an abstract class and should not be used on its own rather it should be extended. @@ -102,6 +102,8 @@ * @private */ this._mask = null; + + this._bounds_ = new BoundsBulder(); } // constructor @@ -350,15 +352,53 @@ DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; /** + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() + */ +DisplayObject.prototype._recursivePostUpdateTransform = function() +{ + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(_tempDisplayObjectParent.transform); + } +}; + +/** * * * Retrieves the bounds of the displayObject as a rectangle object * * @return {PIXI.Rectangle} the rectangular bounding area */ -DisplayObject.prototype.getBounds = function () // jshint unused:false +DisplayObject.prototype.getBounds = function (skipUpdate) { - return math.Rectangle.EMPTY; + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(!this._currentBounds) + { + this.calculateBounds(); + this._currentBounds = this._bounds_.getRectangle(this._bounds); + } + + return this._currentBounds; }; /** @@ -368,7 +408,18 @@ */ DisplayObject.prototype.getLocalBounds = function () { - return this.getBounds(math.Matrix.IDENTITY); + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = _tempDisplayObjectParent.transform; + + var bounds = this.getBounds(); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; }; /** @@ -377,24 +428,29 @@ * @param position {PIXI.Point} The world origin to calculate from * @return {PIXI.Point} A point object representing the position of this object */ -DisplayObject.prototype.toGlobal = function (position) +DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) { - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) + if(!skipUpdate) { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } } // don't need to update the lot - return this.worldTransform.apply(position); + return this.worldTransform.apply(position, point); }; /** @@ -405,25 +461,30 @@ * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) * @return {PIXI.Point} A point object representing the position of this object */ -DisplayObject.prototype.toLocal = function (position, from, point) +DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) { if (from) { - position = from.toGlobal(position); + position = from.toGlobal(position, point, skipUpdate); } - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) + if(! skipUpdate) { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } } // simply apply the matrix.. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 6c575d8..4e37d2d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -5,6 +5,7 @@ Sprite = require('../sprites/Sprite'), math = require('../math'), CONST = require('../const'), + BoundsBuilder = require('../display/BoundsBuilder'), bezierCurveTo = require('./utils/bezierCurveTo'), CanvasRenderer = require('../renderers/canvas/CanvasRenderer'), canvasRenderer, @@ -118,7 +119,7 @@ * @member {PIXI.Rectangle} * @private */ - this._localBounds = new math.Rectangle(0,0,1,1); + this._localBounds = new BoundsBuilder(); /** * Used to detect if the graphics object has changed. If this is set to true then the graphics @@ -774,87 +775,24 @@ * object's worldTransform. * @return {PIXI.Rectangle} the rectangular bounding area */ -Graphics.prototype.getBounds = function (matrix) +Graphics.prototype._calculateBounds = function () { - if(!this._currentBounds) + if (!this.renderable) { - - // return an empty object if the item is a mask! - if (!this.renderable) - { - return math.Rectangle.EMPTY; - } - - if (this.boundsDirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.boundsDirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - this._currentBounds = this._bounds; + return; } - return this._currentBounds; + if (this.boundsDirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.boundsDirty = false; + } + + var lb = this._localBounds; + this._bounds_.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); }; /** @@ -982,11 +920,11 @@ var padding = this.boundsPadding; - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; }; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 0c426a5..62b3a44 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -74,8 +74,7 @@ // for now we go off the filter of the first resolution.. var resolution = filters[0].resolution; var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(); - + var targetBounds = target.filterArea || target.getBounds(true); var sourceFrame = currentState.sourceFrame; var destinationFrame = currentState.destinationFrame; @@ -84,7 +83,16 @@ sourceFrame.width = (((targetBounds.width + padding*2) * resolution) | 0) / resolution; sourceFrame.height = (((targetBounds.height + padding*2)* resolution) | 0) / resolution; - sourceFrame.fit(filterData.stack[0].destinationFrame); + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. + + } + else + { + sourceFrame.fit(filterData.stack[0].destinationFrame); + } destinationFrame.width = sourceFrame.width; destinationFrame.height = sourceFrame.height; @@ -379,7 +387,6 @@ renderTarget.resolution = resolution; renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; }; diff --git a/src/core/display/BoundsBuilder.js b/src/core/display/BoundsBuilder.js new file mode 100644 index 0000000..f86d208 --- /dev/null +++ b/src/core/display/BoundsBuilder.js @@ -0,0 +1,216 @@ +var math = require('../math'), + Rectangle = math.Rectangle; + +/** + * 'Builder' pattern for bounds rectangles + * Axis-Aligned Bounding Box + * It is not a shape! Its mutable thing, no 'EMPTY' or that kind of problems + * + * @class + * @memberof PIXI + */ +function BoundsBuilder() +{ + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; + + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; + + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; + + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; +} + +BoundsBuilder.prototype.constructor = BoundsBuilder; +module.exports = BoundsBuilder; + +BoundsBuilder.prototype.isEmpty = function() +{ + return this.minX > this.maxX || this.minY > this.maxY; +}; + +BoundsBuilder.prototype.clear = function() +{ + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; +}; + +/** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ +BoundsBuilder.prototype.getRectangle = function(tempRect) +{ + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + tempRect = tempRect || new Rectangle(0, 0, 1, 1); + tempRect.x = this.minX; + tempRect.y = this.minY; + tempRect.width = this.maxX - this.minX; + tempRect.height = this.maxY - this.minY; + return tempRect; +}; + +/** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ +BoundsBuilder.prototype.addPoint = function (point) +{ + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); +}; + +/** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.BoundsBuilder} + */ +BoundsBuilder.prototype.addQuad = function(vertices) +{ + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +/** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ +BoundsBuilder.prototype.addFrame = function(transform, x0, y0, x1, y1) +{ + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +/** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ +BoundsBuilder.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) +{ + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +BoundsBuilder.prototype.addBounds = function(bounds) +{ + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; +}; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 7992d77..e69807a 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -1,5 +1,4 @@ -var math = require('../math'), - utils = require('../utils'), +var utils = require('../utils'), DisplayObject = require('./DisplayObject'); /** @@ -364,6 +363,8 @@ */ Container.prototype.updateTransform = function () { + this._currentBounds = null; + if (!this.visible) { return; @@ -379,111 +380,39 @@ this.children[i].updateTransform(); } - this._currentBounds = null; + }; // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; -/** -* Retrieves the bounds of the Container as a rectangle. The bounds calculation takes all visible children into consideration. - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ -Container.prototype.getBounds = function () + +Container.prototype.calculateBounds = function () { - if(!this._currentBounds) + this._bounds_.clear(); + // if we have already done this on THIS frame. + + if(!this.visible) { - - if (this.children.length === 0) - { - return math.Rectangle.EMPTY; - } - - // TODO the bounds have already been calculated this render session so return what we have - - var minX = Infinity; - var minY = Infinity; - - var maxX = -Infinity; - var maxY = -Infinity; - - var childBounds; - var childMaxX; - var childMaxY; - - var childVisible = false; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - childBounds = this.children[i].getBounds(); - if (childBounds === math.Rectangle.EMPTY) { - continue; - } - childVisible = true; - - minX = minX < childBounds.x ? minX : childBounds.x; - minY = minY < childBounds.y ? minY : childBounds.y; - - childMaxX = childBounds.width + childBounds.x; - childMaxY = childBounds.height + childBounds.y; - - maxX = maxX > childMaxX ? maxX : childMaxX; - maxY = maxY > childMaxY ? maxY : childMaxY; - } - - if (!childVisible) - { - this._currentBounds = math.Rectangle.EMPTY; - return this._currentBounds; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.y = minY; - bounds.width = maxX - minX; - bounds.height = maxY - minY; - - this._currentBounds = bounds; + return; } - return this._currentBounds; + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds_.addBounds(child._bounds_); + } }; -Container.prototype.containerGetBounds = Container.prototype.getBounds; - -/** - * Retrieves the non-global local bounds of the Container as a rectangle. - * The calculation takes all visible children into consideration. - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ -Container.prototype.getLocalBounds = function () +Container.prototype._calculateBounds = function () { - var matrixCache = this.transform.worldTransform; - - this.transform.worldTransform = math.Matrix.IDENTITY; - this.transform._worldID++; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } - - this.transform.worldTransform = matrixCache; - this.transform._worldID++; - - this._currentBounds = null; - - return this.getBounds(); + //FILL IN// }; /** diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index ff4fda1..535f833 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,9 +3,9 @@ CONST = require('../const'), TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), + BoundsBulder = require('./BoundsBuilder'), _tempDisplayObjectParent = new DisplayObject(); - /** * The base class for all objects that are rendered on the screen. * This is an abstract class and should not be used on its own rather it should be extended. @@ -102,6 +102,8 @@ * @private */ this._mask = null; + + this._bounds_ = new BoundsBulder(); } // constructor @@ -350,15 +352,53 @@ DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; /** + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() + */ +DisplayObject.prototype._recursivePostUpdateTransform = function() +{ + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(_tempDisplayObjectParent.transform); + } +}; + +/** * * * Retrieves the bounds of the displayObject as a rectangle object * * @return {PIXI.Rectangle} the rectangular bounding area */ -DisplayObject.prototype.getBounds = function () // jshint unused:false +DisplayObject.prototype.getBounds = function (skipUpdate) { - return math.Rectangle.EMPTY; + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(!this._currentBounds) + { + this.calculateBounds(); + this._currentBounds = this._bounds_.getRectangle(this._bounds); + } + + return this._currentBounds; }; /** @@ -368,7 +408,18 @@ */ DisplayObject.prototype.getLocalBounds = function () { - return this.getBounds(math.Matrix.IDENTITY); + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = _tempDisplayObjectParent.transform; + + var bounds = this.getBounds(); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; }; /** @@ -377,24 +428,29 @@ * @param position {PIXI.Point} The world origin to calculate from * @return {PIXI.Point} A point object representing the position of this object */ -DisplayObject.prototype.toGlobal = function (position) +DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) { - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) + if(!skipUpdate) { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } } // don't need to update the lot - return this.worldTransform.apply(position); + return this.worldTransform.apply(position, point); }; /** @@ -405,25 +461,30 @@ * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) * @return {PIXI.Point} A point object representing the position of this object */ -DisplayObject.prototype.toLocal = function (position, from, point) +DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) { if (from) { - position = from.toGlobal(position); + position = from.toGlobal(position, point, skipUpdate); } - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) + if(! skipUpdate) { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } } // simply apply the matrix.. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 6c575d8..4e37d2d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -5,6 +5,7 @@ Sprite = require('../sprites/Sprite'), math = require('../math'), CONST = require('../const'), + BoundsBuilder = require('../display/BoundsBuilder'), bezierCurveTo = require('./utils/bezierCurveTo'), CanvasRenderer = require('../renderers/canvas/CanvasRenderer'), canvasRenderer, @@ -118,7 +119,7 @@ * @member {PIXI.Rectangle} * @private */ - this._localBounds = new math.Rectangle(0,0,1,1); + this._localBounds = new BoundsBuilder(); /** * Used to detect if the graphics object has changed. If this is set to true then the graphics @@ -774,87 +775,24 @@ * object's worldTransform. * @return {PIXI.Rectangle} the rectangular bounding area */ -Graphics.prototype.getBounds = function (matrix) +Graphics.prototype._calculateBounds = function () { - if(!this._currentBounds) + if (!this.renderable) { - - // return an empty object if the item is a mask! - if (!this.renderable) - { - return math.Rectangle.EMPTY; - } - - if (this.boundsDirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.boundsDirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - this._currentBounds = this._bounds; + return; } - return this._currentBounds; + if (this.boundsDirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.boundsDirty = false; + } + + var lb = this._localBounds; + this._bounds_.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); }; /** @@ -982,11 +920,11 @@ var padding = this.boundsPadding; - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; }; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 0c426a5..62b3a44 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -74,8 +74,7 @@ // for now we go off the filter of the first resolution.. var resolution = filters[0].resolution; var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(); - + var targetBounds = target.filterArea || target.getBounds(true); var sourceFrame = currentState.sourceFrame; var destinationFrame = currentState.destinationFrame; @@ -84,7 +83,16 @@ sourceFrame.width = (((targetBounds.width + padding*2) * resolution) | 0) / resolution; sourceFrame.height = (((targetBounds.height + padding*2)* resolution) | 0) / resolution; - sourceFrame.fit(filterData.stack[0].destinationFrame); + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. + + } + else + { + sourceFrame.fit(filterData.stack[0].destinationFrame); + } destinationFrame.width = sourceFrame.width; destinationFrame.height = sourceFrame.height; @@ -379,7 +387,6 @@ renderTarget.resolution = resolution; renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; }; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index f7ccc39..d4ab057 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -341,89 +341,11 @@ renderer.plugins.sprite.render(this); }; - -/** - * Returns the bounds of the Sprite as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Sprite.prototype.getBounds = function () +Sprite.prototype._calculateBounds = function () { - //TODO lookinto caching.. - if(!this._currentBounds) - { - // set the vertex data - this.calculateVertices(); - - // set the vertex data - this.calculateBoundsVertices(); - - var minX, maxX, minY, maxY, - w0, w1, h0, h1, - vertexData = this.vertexData; - - var x1 = vertexData[8]; - var y1 = vertexData[9]; - - var x2 = vertexData[10]; - var y2 = vertexData[11]; - - var x3 = vertexData[12]; - var y3 = vertexData[13]; - - var x4 = vertexData[14]; - var y4 = vertexData[15]; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - // check for children - if(this.children.length) - { - var childBounds = this.containerGetBounds(); - - w0 = childBounds.x; - w1 = childBounds.x + childBounds.width; - h0 = childBounds.y; - h1 = childBounds.y + childBounds.height; - - minX = (minX < w0) ? minX : w0; - minY = (minY < h0) ? minY : h0; - - maxX = (maxX > w1) ? maxX : w1; - maxY = (maxY > h1) ? maxY : h1; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - } - - return this._currentBounds; + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds_.addQuad(this.vertexData); }; /** diff --git a/src/core/display/BoundsBuilder.js b/src/core/display/BoundsBuilder.js new file mode 100644 index 0000000..f86d208 --- /dev/null +++ b/src/core/display/BoundsBuilder.js @@ -0,0 +1,216 @@ +var math = require('../math'), + Rectangle = math.Rectangle; + +/** + * 'Builder' pattern for bounds rectangles + * Axis-Aligned Bounding Box + * It is not a shape! Its mutable thing, no 'EMPTY' or that kind of problems + * + * @class + * @memberof PIXI + */ +function BoundsBuilder() +{ + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; + + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; + + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; + + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; +} + +BoundsBuilder.prototype.constructor = BoundsBuilder; +module.exports = BoundsBuilder; + +BoundsBuilder.prototype.isEmpty = function() +{ + return this.minX > this.maxX || this.minY > this.maxY; +}; + +BoundsBuilder.prototype.clear = function() +{ + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; +}; + +/** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ +BoundsBuilder.prototype.getRectangle = function(tempRect) +{ + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + tempRect = tempRect || new Rectangle(0, 0, 1, 1); + tempRect.x = this.minX; + tempRect.y = this.minY; + tempRect.width = this.maxX - this.minX; + tempRect.height = this.maxY - this.minY; + return tempRect; +}; + +/** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ +BoundsBuilder.prototype.addPoint = function (point) +{ + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); +}; + +/** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.BoundsBuilder} + */ +BoundsBuilder.prototype.addQuad = function(vertices) +{ + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +/** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ +BoundsBuilder.prototype.addFrame = function(transform, x0, y0, x1, y1) +{ + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +/** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ +BoundsBuilder.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) +{ + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +BoundsBuilder.prototype.addBounds = function(bounds) +{ + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; +}; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 7992d77..e69807a 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -1,5 +1,4 @@ -var math = require('../math'), - utils = require('../utils'), +var utils = require('../utils'), DisplayObject = require('./DisplayObject'); /** @@ -364,6 +363,8 @@ */ Container.prototype.updateTransform = function () { + this._currentBounds = null; + if (!this.visible) { return; @@ -379,111 +380,39 @@ this.children[i].updateTransform(); } - this._currentBounds = null; + }; // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; -/** -* Retrieves the bounds of the Container as a rectangle. The bounds calculation takes all visible children into consideration. - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ -Container.prototype.getBounds = function () + +Container.prototype.calculateBounds = function () { - if(!this._currentBounds) + this._bounds_.clear(); + // if we have already done this on THIS frame. + + if(!this.visible) { - - if (this.children.length === 0) - { - return math.Rectangle.EMPTY; - } - - // TODO the bounds have already been calculated this render session so return what we have - - var minX = Infinity; - var minY = Infinity; - - var maxX = -Infinity; - var maxY = -Infinity; - - var childBounds; - var childMaxX; - var childMaxY; - - var childVisible = false; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - childBounds = this.children[i].getBounds(); - if (childBounds === math.Rectangle.EMPTY) { - continue; - } - childVisible = true; - - minX = minX < childBounds.x ? minX : childBounds.x; - minY = minY < childBounds.y ? minY : childBounds.y; - - childMaxX = childBounds.width + childBounds.x; - childMaxY = childBounds.height + childBounds.y; - - maxX = maxX > childMaxX ? maxX : childMaxX; - maxY = maxY > childMaxY ? maxY : childMaxY; - } - - if (!childVisible) - { - this._currentBounds = math.Rectangle.EMPTY; - return this._currentBounds; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.y = minY; - bounds.width = maxX - minX; - bounds.height = maxY - minY; - - this._currentBounds = bounds; + return; } - return this._currentBounds; + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds_.addBounds(child._bounds_); + } }; -Container.prototype.containerGetBounds = Container.prototype.getBounds; - -/** - * Retrieves the non-global local bounds of the Container as a rectangle. - * The calculation takes all visible children into consideration. - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ -Container.prototype.getLocalBounds = function () +Container.prototype._calculateBounds = function () { - var matrixCache = this.transform.worldTransform; - - this.transform.worldTransform = math.Matrix.IDENTITY; - this.transform._worldID++; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } - - this.transform.worldTransform = matrixCache; - this.transform._worldID++; - - this._currentBounds = null; - - return this.getBounds(); + //FILL IN// }; /** diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index ff4fda1..535f833 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,9 +3,9 @@ CONST = require('../const'), TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), + BoundsBulder = require('./BoundsBuilder'), _tempDisplayObjectParent = new DisplayObject(); - /** * The base class for all objects that are rendered on the screen. * This is an abstract class and should not be used on its own rather it should be extended. @@ -102,6 +102,8 @@ * @private */ this._mask = null; + + this._bounds_ = new BoundsBulder(); } // constructor @@ -350,15 +352,53 @@ DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; /** + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() + */ +DisplayObject.prototype._recursivePostUpdateTransform = function() +{ + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(_tempDisplayObjectParent.transform); + } +}; + +/** * * * Retrieves the bounds of the displayObject as a rectangle object * * @return {PIXI.Rectangle} the rectangular bounding area */ -DisplayObject.prototype.getBounds = function () // jshint unused:false +DisplayObject.prototype.getBounds = function (skipUpdate) { - return math.Rectangle.EMPTY; + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(!this._currentBounds) + { + this.calculateBounds(); + this._currentBounds = this._bounds_.getRectangle(this._bounds); + } + + return this._currentBounds; }; /** @@ -368,7 +408,18 @@ */ DisplayObject.prototype.getLocalBounds = function () { - return this.getBounds(math.Matrix.IDENTITY); + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = _tempDisplayObjectParent.transform; + + var bounds = this.getBounds(); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; }; /** @@ -377,24 +428,29 @@ * @param position {PIXI.Point} The world origin to calculate from * @return {PIXI.Point} A point object representing the position of this object */ -DisplayObject.prototype.toGlobal = function (position) +DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) { - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) + if(!skipUpdate) { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } } // don't need to update the lot - return this.worldTransform.apply(position); + return this.worldTransform.apply(position, point); }; /** @@ -405,25 +461,30 @@ * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) * @return {PIXI.Point} A point object representing the position of this object */ -DisplayObject.prototype.toLocal = function (position, from, point) +DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) { if (from) { - position = from.toGlobal(position); + position = from.toGlobal(position, point, skipUpdate); } - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) + if(! skipUpdate) { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } } // simply apply the matrix.. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 6c575d8..4e37d2d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -5,6 +5,7 @@ Sprite = require('../sprites/Sprite'), math = require('../math'), CONST = require('../const'), + BoundsBuilder = require('../display/BoundsBuilder'), bezierCurveTo = require('./utils/bezierCurveTo'), CanvasRenderer = require('../renderers/canvas/CanvasRenderer'), canvasRenderer, @@ -118,7 +119,7 @@ * @member {PIXI.Rectangle} * @private */ - this._localBounds = new math.Rectangle(0,0,1,1); + this._localBounds = new BoundsBuilder(); /** * Used to detect if the graphics object has changed. If this is set to true then the graphics @@ -774,87 +775,24 @@ * object's worldTransform. * @return {PIXI.Rectangle} the rectangular bounding area */ -Graphics.prototype.getBounds = function (matrix) +Graphics.prototype._calculateBounds = function () { - if(!this._currentBounds) + if (!this.renderable) { - - // return an empty object if the item is a mask! - if (!this.renderable) - { - return math.Rectangle.EMPTY; - } - - if (this.boundsDirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.boundsDirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - this._currentBounds = this._bounds; + return; } - return this._currentBounds; + if (this.boundsDirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.boundsDirty = false; + } + + var lb = this._localBounds; + this._bounds_.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); }; /** @@ -982,11 +920,11 @@ var padding = this.boundsPadding; - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; }; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 0c426a5..62b3a44 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -74,8 +74,7 @@ // for now we go off the filter of the first resolution.. var resolution = filters[0].resolution; var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(); - + var targetBounds = target.filterArea || target.getBounds(true); var sourceFrame = currentState.sourceFrame; var destinationFrame = currentState.destinationFrame; @@ -84,7 +83,16 @@ sourceFrame.width = (((targetBounds.width + padding*2) * resolution) | 0) / resolution; sourceFrame.height = (((targetBounds.height + padding*2)* resolution) | 0) / resolution; - sourceFrame.fit(filterData.stack[0].destinationFrame); + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. + + } + else + { + sourceFrame.fit(filterData.stack[0].destinationFrame); + } destinationFrame.width = sourceFrame.width; destinationFrame.height = sourceFrame.height; @@ -379,7 +387,6 @@ renderTarget.resolution = resolution; renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; }; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index f7ccc39..d4ab057 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -341,89 +341,11 @@ renderer.plugins.sprite.render(this); }; - -/** - * Returns the bounds of the Sprite as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Sprite.prototype.getBounds = function () +Sprite.prototype._calculateBounds = function () { - //TODO lookinto caching.. - if(!this._currentBounds) - { - // set the vertex data - this.calculateVertices(); - - // set the vertex data - this.calculateBoundsVertices(); - - var minX, maxX, minY, maxY, - w0, w1, h0, h1, - vertexData = this.vertexData; - - var x1 = vertexData[8]; - var y1 = vertexData[9]; - - var x2 = vertexData[10]; - var y2 = vertexData[11]; - - var x3 = vertexData[12]; - var y3 = vertexData[13]; - - var x4 = vertexData[14]; - var y4 = vertexData[15]; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - // check for children - if(this.children.length) - { - var childBounds = this.containerGetBounds(); - - w0 = childBounds.x; - w1 = childBounds.x + childBounds.width; - h0 = childBounds.y; - h1 = childBounds.y + childBounds.height; - - minX = (minX < w0) ? minX : w0; - minY = (minY < h0) ? minY : h0; - - maxX = (maxX > w1) ? maxX : w1; - maxY = (maxY > h1) ? maxY : h1; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - } - - return this._currentBounds; + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds_.addQuad(this.vertexData); }; /** diff --git a/src/core/text/Text.js b/src/core/text/Text.js index d77e08c..ee875de 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -634,16 +634,14 @@ }; /** - * Returns the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param matrix {PIXI.Matrix} the transformation matrix of the Text - * @return {PIXI.Rectangle} the framing rectangle + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. */ -Text.prototype.getBounds = function (matrix) +Text.prototype._calculateBounds = function () { this.updateText(true); - - return Sprite.prototype.getBounds.call(this, matrix); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds_.addQuad(this.vertexData); }; /** diff --git a/src/core/display/BoundsBuilder.js b/src/core/display/BoundsBuilder.js new file mode 100644 index 0000000..f86d208 --- /dev/null +++ b/src/core/display/BoundsBuilder.js @@ -0,0 +1,216 @@ +var math = require('../math'), + Rectangle = math.Rectangle; + +/** + * 'Builder' pattern for bounds rectangles + * Axis-Aligned Bounding Box + * It is not a shape! Its mutable thing, no 'EMPTY' or that kind of problems + * + * @class + * @memberof PIXI + */ +function BoundsBuilder() +{ + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; + + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; + + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; + + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; +} + +BoundsBuilder.prototype.constructor = BoundsBuilder; +module.exports = BoundsBuilder; + +BoundsBuilder.prototype.isEmpty = function() +{ + return this.minX > this.maxX || this.minY > this.maxY; +}; + +BoundsBuilder.prototype.clear = function() +{ + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; +}; + +/** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ +BoundsBuilder.prototype.getRectangle = function(tempRect) +{ + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + tempRect = tempRect || new Rectangle(0, 0, 1, 1); + tempRect.x = this.minX; + tempRect.y = this.minY; + tempRect.width = this.maxX - this.minX; + tempRect.height = this.maxY - this.minY; + return tempRect; +}; + +/** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ +BoundsBuilder.prototype.addPoint = function (point) +{ + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); +}; + +/** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.BoundsBuilder} + */ +BoundsBuilder.prototype.addQuad = function(vertices) +{ + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +/** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ +BoundsBuilder.prototype.addFrame = function(transform, x0, y0, x1, y1) +{ + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +/** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ +BoundsBuilder.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) +{ + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +BoundsBuilder.prototype.addBounds = function(bounds) +{ + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; +}; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 7992d77..e69807a 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -1,5 +1,4 @@ -var math = require('../math'), - utils = require('../utils'), +var utils = require('../utils'), DisplayObject = require('./DisplayObject'); /** @@ -364,6 +363,8 @@ */ Container.prototype.updateTransform = function () { + this._currentBounds = null; + if (!this.visible) { return; @@ -379,111 +380,39 @@ this.children[i].updateTransform(); } - this._currentBounds = null; + }; // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; -/** -* Retrieves the bounds of the Container as a rectangle. The bounds calculation takes all visible children into consideration. - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ -Container.prototype.getBounds = function () + +Container.prototype.calculateBounds = function () { - if(!this._currentBounds) + this._bounds_.clear(); + // if we have already done this on THIS frame. + + if(!this.visible) { - - if (this.children.length === 0) - { - return math.Rectangle.EMPTY; - } - - // TODO the bounds have already been calculated this render session so return what we have - - var minX = Infinity; - var minY = Infinity; - - var maxX = -Infinity; - var maxY = -Infinity; - - var childBounds; - var childMaxX; - var childMaxY; - - var childVisible = false; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - childBounds = this.children[i].getBounds(); - if (childBounds === math.Rectangle.EMPTY) { - continue; - } - childVisible = true; - - minX = minX < childBounds.x ? minX : childBounds.x; - minY = minY < childBounds.y ? minY : childBounds.y; - - childMaxX = childBounds.width + childBounds.x; - childMaxY = childBounds.height + childBounds.y; - - maxX = maxX > childMaxX ? maxX : childMaxX; - maxY = maxY > childMaxY ? maxY : childMaxY; - } - - if (!childVisible) - { - this._currentBounds = math.Rectangle.EMPTY; - return this._currentBounds; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.y = minY; - bounds.width = maxX - minX; - bounds.height = maxY - minY; - - this._currentBounds = bounds; + return; } - return this._currentBounds; + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds_.addBounds(child._bounds_); + } }; -Container.prototype.containerGetBounds = Container.prototype.getBounds; - -/** - * Retrieves the non-global local bounds of the Container as a rectangle. - * The calculation takes all visible children into consideration. - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ -Container.prototype.getLocalBounds = function () +Container.prototype._calculateBounds = function () { - var matrixCache = this.transform.worldTransform; - - this.transform.worldTransform = math.Matrix.IDENTITY; - this.transform._worldID++; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } - - this.transform.worldTransform = matrixCache; - this.transform._worldID++; - - this._currentBounds = null; - - return this.getBounds(); + //FILL IN// }; /** diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index ff4fda1..535f833 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,9 +3,9 @@ CONST = require('../const'), TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), + BoundsBulder = require('./BoundsBuilder'), _tempDisplayObjectParent = new DisplayObject(); - /** * The base class for all objects that are rendered on the screen. * This is an abstract class and should not be used on its own rather it should be extended. @@ -102,6 +102,8 @@ * @private */ this._mask = null; + + this._bounds_ = new BoundsBulder(); } // constructor @@ -350,15 +352,53 @@ DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; /** + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() + */ +DisplayObject.prototype._recursivePostUpdateTransform = function() +{ + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(_tempDisplayObjectParent.transform); + } +}; + +/** * * * Retrieves the bounds of the displayObject as a rectangle object * * @return {PIXI.Rectangle} the rectangular bounding area */ -DisplayObject.prototype.getBounds = function () // jshint unused:false +DisplayObject.prototype.getBounds = function (skipUpdate) { - return math.Rectangle.EMPTY; + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(!this._currentBounds) + { + this.calculateBounds(); + this._currentBounds = this._bounds_.getRectangle(this._bounds); + } + + return this._currentBounds; }; /** @@ -368,7 +408,18 @@ */ DisplayObject.prototype.getLocalBounds = function () { - return this.getBounds(math.Matrix.IDENTITY); + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = _tempDisplayObjectParent.transform; + + var bounds = this.getBounds(); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; }; /** @@ -377,24 +428,29 @@ * @param position {PIXI.Point} The world origin to calculate from * @return {PIXI.Point} A point object representing the position of this object */ -DisplayObject.prototype.toGlobal = function (position) +DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) { - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) + if(!skipUpdate) { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } } // don't need to update the lot - return this.worldTransform.apply(position); + return this.worldTransform.apply(position, point); }; /** @@ -405,25 +461,30 @@ * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) * @return {PIXI.Point} A point object representing the position of this object */ -DisplayObject.prototype.toLocal = function (position, from, point) +DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) { if (from) { - position = from.toGlobal(position); + position = from.toGlobal(position, point, skipUpdate); } - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) + if(! skipUpdate) { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } } // simply apply the matrix.. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 6c575d8..4e37d2d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -5,6 +5,7 @@ Sprite = require('../sprites/Sprite'), math = require('../math'), CONST = require('../const'), + BoundsBuilder = require('../display/BoundsBuilder'), bezierCurveTo = require('./utils/bezierCurveTo'), CanvasRenderer = require('../renderers/canvas/CanvasRenderer'), canvasRenderer, @@ -118,7 +119,7 @@ * @member {PIXI.Rectangle} * @private */ - this._localBounds = new math.Rectangle(0,0,1,1); + this._localBounds = new BoundsBuilder(); /** * Used to detect if the graphics object has changed. If this is set to true then the graphics @@ -774,87 +775,24 @@ * object's worldTransform. * @return {PIXI.Rectangle} the rectangular bounding area */ -Graphics.prototype.getBounds = function (matrix) +Graphics.prototype._calculateBounds = function () { - if(!this._currentBounds) + if (!this.renderable) { - - // return an empty object if the item is a mask! - if (!this.renderable) - { - return math.Rectangle.EMPTY; - } - - if (this.boundsDirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.boundsDirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - this._currentBounds = this._bounds; + return; } - return this._currentBounds; + if (this.boundsDirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.boundsDirty = false; + } + + var lb = this._localBounds; + this._bounds_.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); }; /** @@ -982,11 +920,11 @@ var padding = this.boundsPadding; - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; }; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 0c426a5..62b3a44 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -74,8 +74,7 @@ // for now we go off the filter of the first resolution.. var resolution = filters[0].resolution; var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(); - + var targetBounds = target.filterArea || target.getBounds(true); var sourceFrame = currentState.sourceFrame; var destinationFrame = currentState.destinationFrame; @@ -84,7 +83,16 @@ sourceFrame.width = (((targetBounds.width + padding*2) * resolution) | 0) / resolution; sourceFrame.height = (((targetBounds.height + padding*2)* resolution) | 0) / resolution; - sourceFrame.fit(filterData.stack[0].destinationFrame); + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. + + } + else + { + sourceFrame.fit(filterData.stack[0].destinationFrame); + } destinationFrame.width = sourceFrame.width; destinationFrame.height = sourceFrame.height; @@ -379,7 +387,6 @@ renderTarget.resolution = resolution; renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; }; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index f7ccc39..d4ab057 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -341,89 +341,11 @@ renderer.plugins.sprite.render(this); }; - -/** - * Returns the bounds of the Sprite as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Sprite.prototype.getBounds = function () +Sprite.prototype._calculateBounds = function () { - //TODO lookinto caching.. - if(!this._currentBounds) - { - // set the vertex data - this.calculateVertices(); - - // set the vertex data - this.calculateBoundsVertices(); - - var minX, maxX, minY, maxY, - w0, w1, h0, h1, - vertexData = this.vertexData; - - var x1 = vertexData[8]; - var y1 = vertexData[9]; - - var x2 = vertexData[10]; - var y2 = vertexData[11]; - - var x3 = vertexData[12]; - var y3 = vertexData[13]; - - var x4 = vertexData[14]; - var y4 = vertexData[15]; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - // check for children - if(this.children.length) - { - var childBounds = this.containerGetBounds(); - - w0 = childBounds.x; - w1 = childBounds.x + childBounds.width; - h0 = childBounds.y; - h1 = childBounds.y + childBounds.height; - - minX = (minX < w0) ? minX : w0; - minY = (minY < h0) ? minY : h0; - - maxX = (maxX > w1) ? maxX : w1; - maxY = (maxY > h1) ? maxY : h1; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - } - - return this._currentBounds; + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds_.addQuad(this.vertexData); }; /** diff --git a/src/core/text/Text.js b/src/core/text/Text.js index d77e08c..ee875de 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -634,16 +634,14 @@ }; /** - * Returns the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param matrix {PIXI.Matrix} the transformation matrix of the Text - * @return {PIXI.Rectangle} the framing rectangle + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. */ -Text.prototype.getBounds = function (matrix) +Text.prototype._calculateBounds = function () { this.updateText(true); - - return Sprite.prototype.getBounds.call(this, matrix); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds_.addQuad(this.vertexData); }; /** diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index f61829a..965f597 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -110,11 +110,6 @@ this.emit('update', this); - //TODO - remove this! - if(this.filterManager) - { - this.filterManager.resize(this.width, this.height); - } }; /** @@ -124,13 +119,6 @@ BaseRenderTexture.prototype.destroy = function () { BaseTexture.prototype.destroy.call(this, true); - - // destroy the filtermanager.. - if(this.filterManager) - { - this.filterManager.destroy(); - } - this.renderer = null; }; diff --git a/src/core/display/BoundsBuilder.js b/src/core/display/BoundsBuilder.js new file mode 100644 index 0000000..f86d208 --- /dev/null +++ b/src/core/display/BoundsBuilder.js @@ -0,0 +1,216 @@ +var math = require('../math'), + Rectangle = math.Rectangle; + +/** + * 'Builder' pattern for bounds rectangles + * Axis-Aligned Bounding Box + * It is not a shape! Its mutable thing, no 'EMPTY' or that kind of problems + * + * @class + * @memberof PIXI + */ +function BoundsBuilder() +{ + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; + + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; + + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; + + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; +} + +BoundsBuilder.prototype.constructor = BoundsBuilder; +module.exports = BoundsBuilder; + +BoundsBuilder.prototype.isEmpty = function() +{ + return this.minX > this.maxX || this.minY > this.maxY; +}; + +BoundsBuilder.prototype.clear = function() +{ + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; +}; + +/** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ +BoundsBuilder.prototype.getRectangle = function(tempRect) +{ + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + tempRect = tempRect || new Rectangle(0, 0, 1, 1); + tempRect.x = this.minX; + tempRect.y = this.minY; + tempRect.width = this.maxX - this.minX; + tempRect.height = this.maxY - this.minY; + return tempRect; +}; + +/** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ +BoundsBuilder.prototype.addPoint = function (point) +{ + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); +}; + +/** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.BoundsBuilder} + */ +BoundsBuilder.prototype.addQuad = function(vertices) +{ + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +/** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ +BoundsBuilder.prototype.addFrame = function(transform, x0, y0, x1, y1) +{ + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +/** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ +BoundsBuilder.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) +{ + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +BoundsBuilder.prototype.addBounds = function(bounds) +{ + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; +}; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 7992d77..e69807a 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -1,5 +1,4 @@ -var math = require('../math'), - utils = require('../utils'), +var utils = require('../utils'), DisplayObject = require('./DisplayObject'); /** @@ -364,6 +363,8 @@ */ Container.prototype.updateTransform = function () { + this._currentBounds = null; + if (!this.visible) { return; @@ -379,111 +380,39 @@ this.children[i].updateTransform(); } - this._currentBounds = null; + }; // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; -/** -* Retrieves the bounds of the Container as a rectangle. The bounds calculation takes all visible children into consideration. - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ -Container.prototype.getBounds = function () + +Container.prototype.calculateBounds = function () { - if(!this._currentBounds) + this._bounds_.clear(); + // if we have already done this on THIS frame. + + if(!this.visible) { - - if (this.children.length === 0) - { - return math.Rectangle.EMPTY; - } - - // TODO the bounds have already been calculated this render session so return what we have - - var minX = Infinity; - var minY = Infinity; - - var maxX = -Infinity; - var maxY = -Infinity; - - var childBounds; - var childMaxX; - var childMaxY; - - var childVisible = false; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - childBounds = this.children[i].getBounds(); - if (childBounds === math.Rectangle.EMPTY) { - continue; - } - childVisible = true; - - minX = minX < childBounds.x ? minX : childBounds.x; - minY = minY < childBounds.y ? minY : childBounds.y; - - childMaxX = childBounds.width + childBounds.x; - childMaxY = childBounds.height + childBounds.y; - - maxX = maxX > childMaxX ? maxX : childMaxX; - maxY = maxY > childMaxY ? maxY : childMaxY; - } - - if (!childVisible) - { - this._currentBounds = math.Rectangle.EMPTY; - return this._currentBounds; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.y = minY; - bounds.width = maxX - minX; - bounds.height = maxY - minY; - - this._currentBounds = bounds; + return; } - return this._currentBounds; + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds_.addBounds(child._bounds_); + } }; -Container.prototype.containerGetBounds = Container.prototype.getBounds; - -/** - * Retrieves the non-global local bounds of the Container as a rectangle. - * The calculation takes all visible children into consideration. - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ -Container.prototype.getLocalBounds = function () +Container.prototype._calculateBounds = function () { - var matrixCache = this.transform.worldTransform; - - this.transform.worldTransform = math.Matrix.IDENTITY; - this.transform._worldID++; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } - - this.transform.worldTransform = matrixCache; - this.transform._worldID++; - - this._currentBounds = null; - - return this.getBounds(); + //FILL IN// }; /** diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index ff4fda1..535f833 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,9 +3,9 @@ CONST = require('../const'), TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), + BoundsBulder = require('./BoundsBuilder'), _tempDisplayObjectParent = new DisplayObject(); - /** * The base class for all objects that are rendered on the screen. * This is an abstract class and should not be used on its own rather it should be extended. @@ -102,6 +102,8 @@ * @private */ this._mask = null; + + this._bounds_ = new BoundsBulder(); } // constructor @@ -350,15 +352,53 @@ DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; /** + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() + */ +DisplayObject.prototype._recursivePostUpdateTransform = function() +{ + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(_tempDisplayObjectParent.transform); + } +}; + +/** * * * Retrieves the bounds of the displayObject as a rectangle object * * @return {PIXI.Rectangle} the rectangular bounding area */ -DisplayObject.prototype.getBounds = function () // jshint unused:false +DisplayObject.prototype.getBounds = function (skipUpdate) { - return math.Rectangle.EMPTY; + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(!this._currentBounds) + { + this.calculateBounds(); + this._currentBounds = this._bounds_.getRectangle(this._bounds); + } + + return this._currentBounds; }; /** @@ -368,7 +408,18 @@ */ DisplayObject.prototype.getLocalBounds = function () { - return this.getBounds(math.Matrix.IDENTITY); + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = _tempDisplayObjectParent.transform; + + var bounds = this.getBounds(); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; }; /** @@ -377,24 +428,29 @@ * @param position {PIXI.Point} The world origin to calculate from * @return {PIXI.Point} A point object representing the position of this object */ -DisplayObject.prototype.toGlobal = function (position) +DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) { - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) + if(!skipUpdate) { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } } // don't need to update the lot - return this.worldTransform.apply(position); + return this.worldTransform.apply(position, point); }; /** @@ -405,25 +461,30 @@ * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) * @return {PIXI.Point} A point object representing the position of this object */ -DisplayObject.prototype.toLocal = function (position, from, point) +DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) { if (from) { - position = from.toGlobal(position); + position = from.toGlobal(position, point, skipUpdate); } - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) + if(! skipUpdate) { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } } // simply apply the matrix.. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 6c575d8..4e37d2d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -5,6 +5,7 @@ Sprite = require('../sprites/Sprite'), math = require('../math'), CONST = require('../const'), + BoundsBuilder = require('../display/BoundsBuilder'), bezierCurveTo = require('./utils/bezierCurveTo'), CanvasRenderer = require('../renderers/canvas/CanvasRenderer'), canvasRenderer, @@ -118,7 +119,7 @@ * @member {PIXI.Rectangle} * @private */ - this._localBounds = new math.Rectangle(0,0,1,1); + this._localBounds = new BoundsBuilder(); /** * Used to detect if the graphics object has changed. If this is set to true then the graphics @@ -774,87 +775,24 @@ * object's worldTransform. * @return {PIXI.Rectangle} the rectangular bounding area */ -Graphics.prototype.getBounds = function (matrix) +Graphics.prototype._calculateBounds = function () { - if(!this._currentBounds) + if (!this.renderable) { - - // return an empty object if the item is a mask! - if (!this.renderable) - { - return math.Rectangle.EMPTY; - } - - if (this.boundsDirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.boundsDirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - this._currentBounds = this._bounds; + return; } - return this._currentBounds; + if (this.boundsDirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.boundsDirty = false; + } + + var lb = this._localBounds; + this._bounds_.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); }; /** @@ -982,11 +920,11 @@ var padding = this.boundsPadding; - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; }; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 0c426a5..62b3a44 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -74,8 +74,7 @@ // for now we go off the filter of the first resolution.. var resolution = filters[0].resolution; var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(); - + var targetBounds = target.filterArea || target.getBounds(true); var sourceFrame = currentState.sourceFrame; var destinationFrame = currentState.destinationFrame; @@ -84,7 +83,16 @@ sourceFrame.width = (((targetBounds.width + padding*2) * resolution) | 0) / resolution; sourceFrame.height = (((targetBounds.height + padding*2)* resolution) | 0) / resolution; - sourceFrame.fit(filterData.stack[0].destinationFrame); + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. + + } + else + { + sourceFrame.fit(filterData.stack[0].destinationFrame); + } destinationFrame.width = sourceFrame.width; destinationFrame.height = sourceFrame.height; @@ -379,7 +387,6 @@ renderTarget.resolution = resolution; renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; }; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index f7ccc39..d4ab057 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -341,89 +341,11 @@ renderer.plugins.sprite.render(this); }; - -/** - * Returns the bounds of the Sprite as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Sprite.prototype.getBounds = function () +Sprite.prototype._calculateBounds = function () { - //TODO lookinto caching.. - if(!this._currentBounds) - { - // set the vertex data - this.calculateVertices(); - - // set the vertex data - this.calculateBoundsVertices(); - - var minX, maxX, minY, maxY, - w0, w1, h0, h1, - vertexData = this.vertexData; - - var x1 = vertexData[8]; - var y1 = vertexData[9]; - - var x2 = vertexData[10]; - var y2 = vertexData[11]; - - var x3 = vertexData[12]; - var y3 = vertexData[13]; - - var x4 = vertexData[14]; - var y4 = vertexData[15]; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - // check for children - if(this.children.length) - { - var childBounds = this.containerGetBounds(); - - w0 = childBounds.x; - w1 = childBounds.x + childBounds.width; - h0 = childBounds.y; - h1 = childBounds.y + childBounds.height; - - minX = (minX < w0) ? minX : w0; - minY = (minY < h0) ? minY : h0; - - maxX = (maxX > w1) ? maxX : w1; - maxY = (maxY > h1) ? maxY : h1; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - } - - return this._currentBounds; + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds_.addQuad(this.vertexData); }; /** diff --git a/src/core/text/Text.js b/src/core/text/Text.js index d77e08c..ee875de 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -634,16 +634,14 @@ }; /** - * Returns the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param matrix {PIXI.Matrix} the transformation matrix of the Text - * @return {PIXI.Rectangle} the framing rectangle + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. */ -Text.prototype.getBounds = function (matrix) +Text.prototype._calculateBounds = function () { this.updateText(true); - - return Sprite.prototype.getBounds.call(this, matrix); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds_.addQuad(this.vertexData); }; /** diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index f61829a..965f597 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -110,11 +110,6 @@ this.emit('update', this); - //TODO - remove this! - if(this.filterManager) - { - this.filterManager.resize(this.width, this.height); - } }; /** @@ -124,13 +119,6 @@ BaseRenderTexture.prototype.destroy = function () { BaseTexture.prototype.destroy.call(this, true); - - // destroy the filtermanager.. - if(this.filterManager) - { - this.filterManager.destroy(); - } - this.renderer = null; }; diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index f9d87ee..9a5d304 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -445,54 +445,10 @@ * @param [matrix=this.worldTransform] {PIXI.Matrix} the transformation matrix of the sprite * @return {PIXI.Rectangle} the framing rectangle */ -Mesh.prototype.getBounds = function (matrix) +Mesh.prototype._calculateBounds = function () { - if (!this._currentBounds) { - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var maxX = -Infinity; - var maxY = -Infinity; - - var minX = Infinity; - var minY = Infinity; - - var vertices = this.vertices; - for (var i = 0, n = vertices.length; i < n; i += 2) { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; - - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - } - - if (minX === -Infinity || maxY === Infinity) { - return core.Rectangle.EMPTY; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - } - - return this._currentBounds; + //TODO - we can cache local bounds and use them if they are dirty (like graphics) + this._bounds_.addVertices(this.transform, this.vertices, 0, this.vertices.length); }; /** diff --git a/src/core/display/BoundsBuilder.js b/src/core/display/BoundsBuilder.js new file mode 100644 index 0000000..f86d208 --- /dev/null +++ b/src/core/display/BoundsBuilder.js @@ -0,0 +1,216 @@ +var math = require('../math'), + Rectangle = math.Rectangle; + +/** + * 'Builder' pattern for bounds rectangles + * Axis-Aligned Bounding Box + * It is not a shape! Its mutable thing, no 'EMPTY' or that kind of problems + * + * @class + * @memberof PIXI + */ +function BoundsBuilder() +{ + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; + + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; + + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; + + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; +} + +BoundsBuilder.prototype.constructor = BoundsBuilder; +module.exports = BoundsBuilder; + +BoundsBuilder.prototype.isEmpty = function() +{ + return this.minX > this.maxX || this.minY > this.maxY; +}; + +BoundsBuilder.prototype.clear = function() +{ + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; +}; + +/** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ +BoundsBuilder.prototype.getRectangle = function(tempRect) +{ + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + tempRect = tempRect || new Rectangle(0, 0, 1, 1); + tempRect.x = this.minX; + tempRect.y = this.minY; + tempRect.width = this.maxX - this.minX; + tempRect.height = this.maxY - this.minY; + return tempRect; +}; + +/** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ +BoundsBuilder.prototype.addPoint = function (point) +{ + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); +}; + +/** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.BoundsBuilder} + */ +BoundsBuilder.prototype.addQuad = function(vertices) +{ + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +/** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ +BoundsBuilder.prototype.addFrame = function(transform, x0, y0, x1, y1) +{ + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +/** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ +BoundsBuilder.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) +{ + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +BoundsBuilder.prototype.addBounds = function(bounds) +{ + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; +}; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 7992d77..e69807a 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -1,5 +1,4 @@ -var math = require('../math'), - utils = require('../utils'), +var utils = require('../utils'), DisplayObject = require('./DisplayObject'); /** @@ -364,6 +363,8 @@ */ Container.prototype.updateTransform = function () { + this._currentBounds = null; + if (!this.visible) { return; @@ -379,111 +380,39 @@ this.children[i].updateTransform(); } - this._currentBounds = null; + }; // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; -/** -* Retrieves the bounds of the Container as a rectangle. The bounds calculation takes all visible children into consideration. - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ -Container.prototype.getBounds = function () + +Container.prototype.calculateBounds = function () { - if(!this._currentBounds) + this._bounds_.clear(); + // if we have already done this on THIS frame. + + if(!this.visible) { - - if (this.children.length === 0) - { - return math.Rectangle.EMPTY; - } - - // TODO the bounds have already been calculated this render session so return what we have - - var minX = Infinity; - var minY = Infinity; - - var maxX = -Infinity; - var maxY = -Infinity; - - var childBounds; - var childMaxX; - var childMaxY; - - var childVisible = false; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - childBounds = this.children[i].getBounds(); - if (childBounds === math.Rectangle.EMPTY) { - continue; - } - childVisible = true; - - minX = minX < childBounds.x ? minX : childBounds.x; - minY = minY < childBounds.y ? minY : childBounds.y; - - childMaxX = childBounds.width + childBounds.x; - childMaxY = childBounds.height + childBounds.y; - - maxX = maxX > childMaxX ? maxX : childMaxX; - maxY = maxY > childMaxY ? maxY : childMaxY; - } - - if (!childVisible) - { - this._currentBounds = math.Rectangle.EMPTY; - return this._currentBounds; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.y = minY; - bounds.width = maxX - minX; - bounds.height = maxY - minY; - - this._currentBounds = bounds; + return; } - return this._currentBounds; + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds_.addBounds(child._bounds_); + } }; -Container.prototype.containerGetBounds = Container.prototype.getBounds; - -/** - * Retrieves the non-global local bounds of the Container as a rectangle. - * The calculation takes all visible children into consideration. - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ -Container.prototype.getLocalBounds = function () +Container.prototype._calculateBounds = function () { - var matrixCache = this.transform.worldTransform; - - this.transform.worldTransform = math.Matrix.IDENTITY; - this.transform._worldID++; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } - - this.transform.worldTransform = matrixCache; - this.transform._worldID++; - - this._currentBounds = null; - - return this.getBounds(); + //FILL IN// }; /** diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index ff4fda1..535f833 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,9 +3,9 @@ CONST = require('../const'), TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), + BoundsBulder = require('./BoundsBuilder'), _tempDisplayObjectParent = new DisplayObject(); - /** * The base class for all objects that are rendered on the screen. * This is an abstract class and should not be used on its own rather it should be extended. @@ -102,6 +102,8 @@ * @private */ this._mask = null; + + this._bounds_ = new BoundsBulder(); } // constructor @@ -350,15 +352,53 @@ DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; /** + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() + */ +DisplayObject.prototype._recursivePostUpdateTransform = function() +{ + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(_tempDisplayObjectParent.transform); + } +}; + +/** * * * Retrieves the bounds of the displayObject as a rectangle object * * @return {PIXI.Rectangle} the rectangular bounding area */ -DisplayObject.prototype.getBounds = function () // jshint unused:false +DisplayObject.prototype.getBounds = function (skipUpdate) { - return math.Rectangle.EMPTY; + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(!this._currentBounds) + { + this.calculateBounds(); + this._currentBounds = this._bounds_.getRectangle(this._bounds); + } + + return this._currentBounds; }; /** @@ -368,7 +408,18 @@ */ DisplayObject.prototype.getLocalBounds = function () { - return this.getBounds(math.Matrix.IDENTITY); + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = _tempDisplayObjectParent.transform; + + var bounds = this.getBounds(); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; }; /** @@ -377,24 +428,29 @@ * @param position {PIXI.Point} The world origin to calculate from * @return {PIXI.Point} A point object representing the position of this object */ -DisplayObject.prototype.toGlobal = function (position) +DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) { - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) + if(!skipUpdate) { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } } // don't need to update the lot - return this.worldTransform.apply(position); + return this.worldTransform.apply(position, point); }; /** @@ -405,25 +461,30 @@ * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) * @return {PIXI.Point} A point object representing the position of this object */ -DisplayObject.prototype.toLocal = function (position, from, point) +DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) { if (from) { - position = from.toGlobal(position); + position = from.toGlobal(position, point, skipUpdate); } - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) + if(! skipUpdate) { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } } // simply apply the matrix.. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 6c575d8..4e37d2d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -5,6 +5,7 @@ Sprite = require('../sprites/Sprite'), math = require('../math'), CONST = require('../const'), + BoundsBuilder = require('../display/BoundsBuilder'), bezierCurveTo = require('./utils/bezierCurveTo'), CanvasRenderer = require('../renderers/canvas/CanvasRenderer'), canvasRenderer, @@ -118,7 +119,7 @@ * @member {PIXI.Rectangle} * @private */ - this._localBounds = new math.Rectangle(0,0,1,1); + this._localBounds = new BoundsBuilder(); /** * Used to detect if the graphics object has changed. If this is set to true then the graphics @@ -774,87 +775,24 @@ * object's worldTransform. * @return {PIXI.Rectangle} the rectangular bounding area */ -Graphics.prototype.getBounds = function (matrix) +Graphics.prototype._calculateBounds = function () { - if(!this._currentBounds) + if (!this.renderable) { - - // return an empty object if the item is a mask! - if (!this.renderable) - { - return math.Rectangle.EMPTY; - } - - if (this.boundsDirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.boundsDirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - this._currentBounds = this._bounds; + return; } - return this._currentBounds; + if (this.boundsDirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.boundsDirty = false; + } + + var lb = this._localBounds; + this._bounds_.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); }; /** @@ -982,11 +920,11 @@ var padding = this.boundsPadding; - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; }; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 0c426a5..62b3a44 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -74,8 +74,7 @@ // for now we go off the filter of the first resolution.. var resolution = filters[0].resolution; var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(); - + var targetBounds = target.filterArea || target.getBounds(true); var sourceFrame = currentState.sourceFrame; var destinationFrame = currentState.destinationFrame; @@ -84,7 +83,16 @@ sourceFrame.width = (((targetBounds.width + padding*2) * resolution) | 0) / resolution; sourceFrame.height = (((targetBounds.height + padding*2)* resolution) | 0) / resolution; - sourceFrame.fit(filterData.stack[0].destinationFrame); + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. + + } + else + { + sourceFrame.fit(filterData.stack[0].destinationFrame); + } destinationFrame.width = sourceFrame.width; destinationFrame.height = sourceFrame.height; @@ -379,7 +387,6 @@ renderTarget.resolution = resolution; renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; }; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index f7ccc39..d4ab057 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -341,89 +341,11 @@ renderer.plugins.sprite.render(this); }; - -/** - * Returns the bounds of the Sprite as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Sprite.prototype.getBounds = function () +Sprite.prototype._calculateBounds = function () { - //TODO lookinto caching.. - if(!this._currentBounds) - { - // set the vertex data - this.calculateVertices(); - - // set the vertex data - this.calculateBoundsVertices(); - - var minX, maxX, minY, maxY, - w0, w1, h0, h1, - vertexData = this.vertexData; - - var x1 = vertexData[8]; - var y1 = vertexData[9]; - - var x2 = vertexData[10]; - var y2 = vertexData[11]; - - var x3 = vertexData[12]; - var y3 = vertexData[13]; - - var x4 = vertexData[14]; - var y4 = vertexData[15]; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - // check for children - if(this.children.length) - { - var childBounds = this.containerGetBounds(); - - w0 = childBounds.x; - w1 = childBounds.x + childBounds.width; - h0 = childBounds.y; - h1 = childBounds.y + childBounds.height; - - minX = (minX < w0) ? minX : w0; - minY = (minY < h0) ? minY : h0; - - maxX = (maxX > w1) ? maxX : w1; - maxY = (maxY > h1) ? maxY : h1; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - } - - return this._currentBounds; + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds_.addQuad(this.vertexData); }; /** diff --git a/src/core/text/Text.js b/src/core/text/Text.js index d77e08c..ee875de 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -634,16 +634,14 @@ }; /** - * Returns the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param matrix {PIXI.Matrix} the transformation matrix of the Text - * @return {PIXI.Rectangle} the framing rectangle + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. */ -Text.prototype.getBounds = function (matrix) +Text.prototype._calculateBounds = function () { this.updateText(true); - - return Sprite.prototype.getBounds.call(this, matrix); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds_.addQuad(this.vertexData); }; /** diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index f61829a..965f597 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -110,11 +110,6 @@ this.emit('update', this); - //TODO - remove this! - if(this.filterManager) - { - this.filterManager.resize(this.width, this.height); - } }; /** @@ -124,13 +119,6 @@ BaseRenderTexture.prototype.destroy = function () { BaseTexture.prototype.destroy.call(this, true); - - // destroy the filtermanager.. - if(this.filterManager) - { - this.filterManager.destroy(); - } - this.renderer = null; }; diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index f9d87ee..9a5d304 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -445,54 +445,10 @@ * @param [matrix=this.worldTransform] {PIXI.Matrix} the transformation matrix of the sprite * @return {PIXI.Rectangle} the framing rectangle */ -Mesh.prototype.getBounds = function (matrix) +Mesh.prototype._calculateBounds = function () { - if (!this._currentBounds) { - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var maxX = -Infinity; - var maxY = -Infinity; - - var minX = Infinity; - var minY = Infinity; - - var vertices = this.vertices; - for (var i = 0, n = vertices.length; i < n; i += 2) { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; - - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - } - - if (minX === -Infinity || maxY === Infinity) { - return core.Rectangle.EMPTY; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - } - - return this._currentBounds; + //TODO - we can cache local bounds and use them if they are dirty (like graphics) + this._bounds_.addVertices(this.transform, this.vertices, 0, this.vertices.length); }; /** diff --git a/test/unit/core/Bounds.test.js b/test/unit/core/Bounds.test.js new file mode 100644 index 0000000..886b75b --- /dev/null +++ b/test/unit/core/Bounds.test.js @@ -0,0 +1,327 @@ +describe('getBounds', function () { + + + it('should register correct width and height with a LOADED Sprite', function() { + var parent = new PIXI.Container(); + var texture = PIXI.RenderTexture.create(10, 10); + + var sprite = new PIXI.Sprite(texture); + + parent.addChild(sprite); + + + var bounds = sprite.getBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + sprite.position.x = 20; + sprite.position.y = 20; + + sprite.scale.x = 2; + sprite.scale.y = 2; + + var bounds = sprite.getBounds(); + + expect(bounds.x).to.equal(20); + expect(bounds.y).to.equal(20); + expect(bounds.width).to.equal(20); + expect(bounds.height).to.equal(20); + + + var bounds = sprite.getBounds(true); + + expect(bounds.x).to.equal(20); + expect(bounds.y).to.equal(20); + expect(bounds.width).to.equal(20); + expect(bounds.height).to.equal(20); + + }); + + it('should register correct width and height with Graphics', function() { + var parent = new PIXI.Container(); + + var graphics = new PIXI.Graphics(); + + var bounds = graphics.getBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(0); + expect(bounds.height).to.equal(0); + + graphics.beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + parent.addChild(graphics); + + var bounds = graphics.getBounds(); + + expect(bounds.x).to.equal(-10); + expect(bounds.y).to.equal(-10); + expect(bounds.width).to.equal(20); + expect(bounds.height).to.equal(20); + + graphics.position.x = 20; + graphics.position.y = 20; + + graphics.scale.x = 2; + graphics.scale.y = 2; + + var bounds = graphics.getBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(40); + expect(bounds.height).to.equal(40); + + var bounds = graphics.getBounds(true); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(40); + expect(bounds.height).to.equal(40); + + }); + + it('should register correct width and height with an empty Container', function() { + var parent = new PIXI.Container(); + + var container = new PIXI.Container();//Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + parent.addChild(container); + + var bounds = container.getBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(0); + expect(bounds.height).to.equal(0); + + container.position.x = 20; + container.position.y = 20; + + container.scale.x = 2; + container.scale.y = 2; + + var bounds = container.getBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(0); + expect(bounds.height).to.equal(0); + + }); + + it('should register correct width and height with a Container', function() { + var parent = new PIXI.Container(); + + var container = new PIXI.Container();//Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var graphics = new PIXI.Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var texture = PIXI.RenderTexture.create(10, 10); + var sprite = new PIXI.Sprite(texture); + + container.addChild(sprite); + container.addChild(graphics); + + parent.addChild(container); + + sprite.position.x = 30; + sprite.position.y = 20; + graphics.position.x = 100; + graphics.position.y = 100; + + var bounds = container.getBounds(); + + expect(bounds.x).to.equal(30); + expect(bounds.y).to.equal(20); + expect(bounds.width).to.equal(80); + expect(bounds.height).to.equal(90); + + container.rotation = 0.1; + + var bounds = container.getBounds(); + + expect(bounds.x | 0).to.equal(26); + expect(bounds.y | 0).to.equal(22); + expect(bounds.width | 0).to.equal(73); + expect(bounds.height | 0).to.equal(97); + + var bounds = container.getBounds(true); + + expect(bounds.x | 0).to.equal(26); + expect(bounds.y | 0).to.equal(22); + expect(bounds.width | 0).to.equal(73); + expect(bounds.height | 0).to.equal(97); + + + }); + + it('should register correct width and height with an item that has already had its parent Container transformed', function() { + var parent = new PIXI.Container(); + + var container = new PIXI.Container();//Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var graphics = new PIXI.Graphics().beginFill(0xFF0000).drawRect(0, 0, 10, 10);//texture); + + + parent.addChild(container); + container.addChild(graphics); + + container.position.x = 100; + container.position.y = 100; + + var bounds = container.getBounds(); + + expect(bounds.x).to.equal(100); + expect(bounds.y).to.equal(100); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + + var bounds = graphics.getBounds(true); + + expect(bounds.x).to.equal(100); + expect(bounds.y).to.equal(100); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + + + + }); + + it('should register correct width and height with a Mesh', function() { + var parent = new PIXI.Container(); + + var texture = PIXI.RenderTexture.create(10, 10); + + var plane = new PIXI.mesh.Plane(texture); + + parent.addChild(plane); + + plane.position.x = 20; + plane.position.y = 20; + + var bounds = plane.getBounds(); + + expect(bounds.x).to.equal(20); + expect(bounds.y).to.equal(20); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + plane.scale.x = 2; + plane.scale.y = 2; + + var bounds = plane.getBounds(); + + expect(bounds.x).to.equal(20); + expect(bounds.y).to.equal(20); + expect(bounds.width).to.equal(20); + expect(bounds.height).to.equal(20); + + }); + + it('should register correct width and height with an a DisplayObject is visible false', function() { + + var parent = new PIXI.Container(); + + var container = new PIXI.Container();//Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var graphics = new PIXI.Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var texture = PIXI.RenderTexture.create(10, 10); + var sprite = new PIXI.Sprite(texture); + + container.addChild(sprite); + container.addChild(graphics); + + parent.addChild(container); + + sprite.position.x = 30; + sprite.position.y = 20; + graphics.position.x = 100; + graphics.position.y = 100; + + graphics.visible = false; + + var bounds = container.getBounds(); + + expect(bounds.x).to.equal(30); + expect(bounds.y).to.equal(20); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + container.visible = false; + + var bounds = container.getBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(0); + expect(bounds.height).to.equal(0); + + + }); + + it('should register correct width and height with an a DisplayObject parent has moved', function() { + + var parent = new PIXI.Container(); + + var container = new PIXI.Container();//Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var graphics = new PIXI.Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10);//texture); + + container.addChild(graphics); + + parent.addChild(container); + + // graphics.position.x = 100; + // graphics.position.y = 100; + container.position.x -= 100; + container.position.y -= 100; + + var bounds = graphics.getBounds(); + + expect(bounds.x).to.equal(-110); + expect(bounds.y).to.equal(-110); + expect(bounds.width).to.equal(20); + expect(bounds.height).to.equal(20); + + + }); + + it('should register correct width and height with an a Text Object', function() { + + var parent = new PIXI.Container(); + + var container = new PIXI.Container();//Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var text = new PIXI.Text('i am some text'); + + container.addChild(text); + + parent.addChild(container); + + var bounds = text.getBounds(); + var bx = bounds.width; + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.be.greaterThan(0); + expect(bounds.height).to.greaterThan(0); + + text.text = 'hello!'; + + var bounds = text.getBounds(); + + // this variable seems to be different on different devices. a font thing? + expect(bounds.width).to.not.equal(bx); + + }); + + + }); diff --git a/src/core/display/BoundsBuilder.js b/src/core/display/BoundsBuilder.js new file mode 100644 index 0000000..f86d208 --- /dev/null +++ b/src/core/display/BoundsBuilder.js @@ -0,0 +1,216 @@ +var math = require('../math'), + Rectangle = math.Rectangle; + +/** + * 'Builder' pattern for bounds rectangles + * Axis-Aligned Bounding Box + * It is not a shape! Its mutable thing, no 'EMPTY' or that kind of problems + * + * @class + * @memberof PIXI + */ +function BoundsBuilder() +{ + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; + + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; + + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; + + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; +} + +BoundsBuilder.prototype.constructor = BoundsBuilder; +module.exports = BoundsBuilder; + +BoundsBuilder.prototype.isEmpty = function() +{ + return this.minX > this.maxX || this.minY > this.maxY; +}; + +BoundsBuilder.prototype.clear = function() +{ + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; +}; + +/** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ +BoundsBuilder.prototype.getRectangle = function(tempRect) +{ + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + tempRect = tempRect || new Rectangle(0, 0, 1, 1); + tempRect.x = this.minX; + tempRect.y = this.minY; + tempRect.width = this.maxX - this.minX; + tempRect.height = this.maxY - this.minY; + return tempRect; +}; + +/** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ +BoundsBuilder.prototype.addPoint = function (point) +{ + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); +}; + +/** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.BoundsBuilder} + */ +BoundsBuilder.prototype.addQuad = function(vertices) +{ + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +/** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ +BoundsBuilder.prototype.addFrame = function(transform, x0, y0, x1, y1) +{ + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +/** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ +BoundsBuilder.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) +{ + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +BoundsBuilder.prototype.addBounds = function(bounds) +{ + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; +}; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 7992d77..e69807a 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -1,5 +1,4 @@ -var math = require('../math'), - utils = require('../utils'), +var utils = require('../utils'), DisplayObject = require('./DisplayObject'); /** @@ -364,6 +363,8 @@ */ Container.prototype.updateTransform = function () { + this._currentBounds = null; + if (!this.visible) { return; @@ -379,111 +380,39 @@ this.children[i].updateTransform(); } - this._currentBounds = null; + }; // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; -/** -* Retrieves the bounds of the Container as a rectangle. The bounds calculation takes all visible children into consideration. - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ -Container.prototype.getBounds = function () + +Container.prototype.calculateBounds = function () { - if(!this._currentBounds) + this._bounds_.clear(); + // if we have already done this on THIS frame. + + if(!this.visible) { - - if (this.children.length === 0) - { - return math.Rectangle.EMPTY; - } - - // TODO the bounds have already been calculated this render session so return what we have - - var minX = Infinity; - var minY = Infinity; - - var maxX = -Infinity; - var maxY = -Infinity; - - var childBounds; - var childMaxX; - var childMaxY; - - var childVisible = false; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - childBounds = this.children[i].getBounds(); - if (childBounds === math.Rectangle.EMPTY) { - continue; - } - childVisible = true; - - minX = minX < childBounds.x ? minX : childBounds.x; - minY = minY < childBounds.y ? minY : childBounds.y; - - childMaxX = childBounds.width + childBounds.x; - childMaxY = childBounds.height + childBounds.y; - - maxX = maxX > childMaxX ? maxX : childMaxX; - maxY = maxY > childMaxY ? maxY : childMaxY; - } - - if (!childVisible) - { - this._currentBounds = math.Rectangle.EMPTY; - return this._currentBounds; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.y = minY; - bounds.width = maxX - minX; - bounds.height = maxY - minY; - - this._currentBounds = bounds; + return; } - return this._currentBounds; + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds_.addBounds(child._bounds_); + } }; -Container.prototype.containerGetBounds = Container.prototype.getBounds; - -/** - * Retrieves the non-global local bounds of the Container as a rectangle. - * The calculation takes all visible children into consideration. - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ -Container.prototype.getLocalBounds = function () +Container.prototype._calculateBounds = function () { - var matrixCache = this.transform.worldTransform; - - this.transform.worldTransform = math.Matrix.IDENTITY; - this.transform._worldID++; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } - - this.transform.worldTransform = matrixCache; - this.transform._worldID++; - - this._currentBounds = null; - - return this.getBounds(); + //FILL IN// }; /** diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index ff4fda1..535f833 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,9 +3,9 @@ CONST = require('../const'), TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), + BoundsBulder = require('./BoundsBuilder'), _tempDisplayObjectParent = new DisplayObject(); - /** * The base class for all objects that are rendered on the screen. * This is an abstract class and should not be used on its own rather it should be extended. @@ -102,6 +102,8 @@ * @private */ this._mask = null; + + this._bounds_ = new BoundsBulder(); } // constructor @@ -350,15 +352,53 @@ DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; /** + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() + */ +DisplayObject.prototype._recursivePostUpdateTransform = function() +{ + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(_tempDisplayObjectParent.transform); + } +}; + +/** * * * Retrieves the bounds of the displayObject as a rectangle object * * @return {PIXI.Rectangle} the rectangular bounding area */ -DisplayObject.prototype.getBounds = function () // jshint unused:false +DisplayObject.prototype.getBounds = function (skipUpdate) { - return math.Rectangle.EMPTY; + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(!this._currentBounds) + { + this.calculateBounds(); + this._currentBounds = this._bounds_.getRectangle(this._bounds); + } + + return this._currentBounds; }; /** @@ -368,7 +408,18 @@ */ DisplayObject.prototype.getLocalBounds = function () { - return this.getBounds(math.Matrix.IDENTITY); + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = _tempDisplayObjectParent.transform; + + var bounds = this.getBounds(); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; }; /** @@ -377,24 +428,29 @@ * @param position {PIXI.Point} The world origin to calculate from * @return {PIXI.Point} A point object representing the position of this object */ -DisplayObject.prototype.toGlobal = function (position) +DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) { - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) + if(!skipUpdate) { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } } // don't need to update the lot - return this.worldTransform.apply(position); + return this.worldTransform.apply(position, point); }; /** @@ -405,25 +461,30 @@ * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) * @return {PIXI.Point} A point object representing the position of this object */ -DisplayObject.prototype.toLocal = function (position, from, point) +DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) { if (from) { - position = from.toGlobal(position); + position = from.toGlobal(position, point, skipUpdate); } - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) + if(! skipUpdate) { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } } // simply apply the matrix.. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 6c575d8..4e37d2d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -5,6 +5,7 @@ Sprite = require('../sprites/Sprite'), math = require('../math'), CONST = require('../const'), + BoundsBuilder = require('../display/BoundsBuilder'), bezierCurveTo = require('./utils/bezierCurveTo'), CanvasRenderer = require('../renderers/canvas/CanvasRenderer'), canvasRenderer, @@ -118,7 +119,7 @@ * @member {PIXI.Rectangle} * @private */ - this._localBounds = new math.Rectangle(0,0,1,1); + this._localBounds = new BoundsBuilder(); /** * Used to detect if the graphics object has changed. If this is set to true then the graphics @@ -774,87 +775,24 @@ * object's worldTransform. * @return {PIXI.Rectangle} the rectangular bounding area */ -Graphics.prototype.getBounds = function (matrix) +Graphics.prototype._calculateBounds = function () { - if(!this._currentBounds) + if (!this.renderable) { - - // return an empty object if the item is a mask! - if (!this.renderable) - { - return math.Rectangle.EMPTY; - } - - if (this.boundsDirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.boundsDirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - this._currentBounds = this._bounds; + return; } - return this._currentBounds; + if (this.boundsDirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.boundsDirty = false; + } + + var lb = this._localBounds; + this._bounds_.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); }; /** @@ -982,11 +920,11 @@ var padding = this.boundsPadding; - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; }; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 0c426a5..62b3a44 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -74,8 +74,7 @@ // for now we go off the filter of the first resolution.. var resolution = filters[0].resolution; var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(); - + var targetBounds = target.filterArea || target.getBounds(true); var sourceFrame = currentState.sourceFrame; var destinationFrame = currentState.destinationFrame; @@ -84,7 +83,16 @@ sourceFrame.width = (((targetBounds.width + padding*2) * resolution) | 0) / resolution; sourceFrame.height = (((targetBounds.height + padding*2)* resolution) | 0) / resolution; - sourceFrame.fit(filterData.stack[0].destinationFrame); + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. + + } + else + { + sourceFrame.fit(filterData.stack[0].destinationFrame); + } destinationFrame.width = sourceFrame.width; destinationFrame.height = sourceFrame.height; @@ -379,7 +387,6 @@ renderTarget.resolution = resolution; renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; }; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index f7ccc39..d4ab057 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -341,89 +341,11 @@ renderer.plugins.sprite.render(this); }; - -/** - * Returns the bounds of the Sprite as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Sprite.prototype.getBounds = function () +Sprite.prototype._calculateBounds = function () { - //TODO lookinto caching.. - if(!this._currentBounds) - { - // set the vertex data - this.calculateVertices(); - - // set the vertex data - this.calculateBoundsVertices(); - - var minX, maxX, minY, maxY, - w0, w1, h0, h1, - vertexData = this.vertexData; - - var x1 = vertexData[8]; - var y1 = vertexData[9]; - - var x2 = vertexData[10]; - var y2 = vertexData[11]; - - var x3 = vertexData[12]; - var y3 = vertexData[13]; - - var x4 = vertexData[14]; - var y4 = vertexData[15]; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - // check for children - if(this.children.length) - { - var childBounds = this.containerGetBounds(); - - w0 = childBounds.x; - w1 = childBounds.x + childBounds.width; - h0 = childBounds.y; - h1 = childBounds.y + childBounds.height; - - minX = (minX < w0) ? minX : w0; - minY = (minY < h0) ? minY : h0; - - maxX = (maxX > w1) ? maxX : w1; - maxY = (maxY > h1) ? maxY : h1; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - } - - return this._currentBounds; + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds_.addQuad(this.vertexData); }; /** diff --git a/src/core/text/Text.js b/src/core/text/Text.js index d77e08c..ee875de 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -634,16 +634,14 @@ }; /** - * Returns the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param matrix {PIXI.Matrix} the transformation matrix of the Text - * @return {PIXI.Rectangle} the framing rectangle + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. */ -Text.prototype.getBounds = function (matrix) +Text.prototype._calculateBounds = function () { this.updateText(true); - - return Sprite.prototype.getBounds.call(this, matrix); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds_.addQuad(this.vertexData); }; /** diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index f61829a..965f597 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -110,11 +110,6 @@ this.emit('update', this); - //TODO - remove this! - if(this.filterManager) - { - this.filterManager.resize(this.width, this.height); - } }; /** @@ -124,13 +119,6 @@ BaseRenderTexture.prototype.destroy = function () { BaseTexture.prototype.destroy.call(this, true); - - // destroy the filtermanager.. - if(this.filterManager) - { - this.filterManager.destroy(); - } - this.renderer = null; }; diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index f9d87ee..9a5d304 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -445,54 +445,10 @@ * @param [matrix=this.worldTransform] {PIXI.Matrix} the transformation matrix of the sprite * @return {PIXI.Rectangle} the framing rectangle */ -Mesh.prototype.getBounds = function (matrix) +Mesh.prototype._calculateBounds = function () { - if (!this._currentBounds) { - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var maxX = -Infinity; - var maxY = -Infinity; - - var minX = Infinity; - var minY = Infinity; - - var vertices = this.vertices; - for (var i = 0, n = vertices.length; i < n; i += 2) { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; - - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - } - - if (minX === -Infinity || maxY === Infinity) { - return core.Rectangle.EMPTY; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - } - - return this._currentBounds; + //TODO - we can cache local bounds and use them if they are dirty (like graphics) + this._bounds_.addVertices(this.transform, this.vertices, 0, this.vertices.length); }; /** diff --git a/test/unit/core/Bounds.test.js b/test/unit/core/Bounds.test.js new file mode 100644 index 0000000..886b75b --- /dev/null +++ b/test/unit/core/Bounds.test.js @@ -0,0 +1,327 @@ +describe('getBounds', function () { + + + it('should register correct width and height with a LOADED Sprite', function() { + var parent = new PIXI.Container(); + var texture = PIXI.RenderTexture.create(10, 10); + + var sprite = new PIXI.Sprite(texture); + + parent.addChild(sprite); + + + var bounds = sprite.getBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + sprite.position.x = 20; + sprite.position.y = 20; + + sprite.scale.x = 2; + sprite.scale.y = 2; + + var bounds = sprite.getBounds(); + + expect(bounds.x).to.equal(20); + expect(bounds.y).to.equal(20); + expect(bounds.width).to.equal(20); + expect(bounds.height).to.equal(20); + + + var bounds = sprite.getBounds(true); + + expect(bounds.x).to.equal(20); + expect(bounds.y).to.equal(20); + expect(bounds.width).to.equal(20); + expect(bounds.height).to.equal(20); + + }); + + it('should register correct width and height with Graphics', function() { + var parent = new PIXI.Container(); + + var graphics = new PIXI.Graphics(); + + var bounds = graphics.getBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(0); + expect(bounds.height).to.equal(0); + + graphics.beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + parent.addChild(graphics); + + var bounds = graphics.getBounds(); + + expect(bounds.x).to.equal(-10); + expect(bounds.y).to.equal(-10); + expect(bounds.width).to.equal(20); + expect(bounds.height).to.equal(20); + + graphics.position.x = 20; + graphics.position.y = 20; + + graphics.scale.x = 2; + graphics.scale.y = 2; + + var bounds = graphics.getBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(40); + expect(bounds.height).to.equal(40); + + var bounds = graphics.getBounds(true); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(40); + expect(bounds.height).to.equal(40); + + }); + + it('should register correct width and height with an empty Container', function() { + var parent = new PIXI.Container(); + + var container = new PIXI.Container();//Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + parent.addChild(container); + + var bounds = container.getBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(0); + expect(bounds.height).to.equal(0); + + container.position.x = 20; + container.position.y = 20; + + container.scale.x = 2; + container.scale.y = 2; + + var bounds = container.getBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(0); + expect(bounds.height).to.equal(0); + + }); + + it('should register correct width and height with a Container', function() { + var parent = new PIXI.Container(); + + var container = new PIXI.Container();//Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var graphics = new PIXI.Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var texture = PIXI.RenderTexture.create(10, 10); + var sprite = new PIXI.Sprite(texture); + + container.addChild(sprite); + container.addChild(graphics); + + parent.addChild(container); + + sprite.position.x = 30; + sprite.position.y = 20; + graphics.position.x = 100; + graphics.position.y = 100; + + var bounds = container.getBounds(); + + expect(bounds.x).to.equal(30); + expect(bounds.y).to.equal(20); + expect(bounds.width).to.equal(80); + expect(bounds.height).to.equal(90); + + container.rotation = 0.1; + + var bounds = container.getBounds(); + + expect(bounds.x | 0).to.equal(26); + expect(bounds.y | 0).to.equal(22); + expect(bounds.width | 0).to.equal(73); + expect(bounds.height | 0).to.equal(97); + + var bounds = container.getBounds(true); + + expect(bounds.x | 0).to.equal(26); + expect(bounds.y | 0).to.equal(22); + expect(bounds.width | 0).to.equal(73); + expect(bounds.height | 0).to.equal(97); + + + }); + + it('should register correct width and height with an item that has already had its parent Container transformed', function() { + var parent = new PIXI.Container(); + + var container = new PIXI.Container();//Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var graphics = new PIXI.Graphics().beginFill(0xFF0000).drawRect(0, 0, 10, 10);//texture); + + + parent.addChild(container); + container.addChild(graphics); + + container.position.x = 100; + container.position.y = 100; + + var bounds = container.getBounds(); + + expect(bounds.x).to.equal(100); + expect(bounds.y).to.equal(100); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + + var bounds = graphics.getBounds(true); + + expect(bounds.x).to.equal(100); + expect(bounds.y).to.equal(100); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + + + + }); + + it('should register correct width and height with a Mesh', function() { + var parent = new PIXI.Container(); + + var texture = PIXI.RenderTexture.create(10, 10); + + var plane = new PIXI.mesh.Plane(texture); + + parent.addChild(plane); + + plane.position.x = 20; + plane.position.y = 20; + + var bounds = plane.getBounds(); + + expect(bounds.x).to.equal(20); + expect(bounds.y).to.equal(20); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + plane.scale.x = 2; + plane.scale.y = 2; + + var bounds = plane.getBounds(); + + expect(bounds.x).to.equal(20); + expect(bounds.y).to.equal(20); + expect(bounds.width).to.equal(20); + expect(bounds.height).to.equal(20); + + }); + + it('should register correct width and height with an a DisplayObject is visible false', function() { + + var parent = new PIXI.Container(); + + var container = new PIXI.Container();//Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var graphics = new PIXI.Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var texture = PIXI.RenderTexture.create(10, 10); + var sprite = new PIXI.Sprite(texture); + + container.addChild(sprite); + container.addChild(graphics); + + parent.addChild(container); + + sprite.position.x = 30; + sprite.position.y = 20; + graphics.position.x = 100; + graphics.position.y = 100; + + graphics.visible = false; + + var bounds = container.getBounds(); + + expect(bounds.x).to.equal(30); + expect(bounds.y).to.equal(20); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + container.visible = false; + + var bounds = container.getBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(0); + expect(bounds.height).to.equal(0); + + + }); + + it('should register correct width and height with an a DisplayObject parent has moved', function() { + + var parent = new PIXI.Container(); + + var container = new PIXI.Container();//Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var graphics = new PIXI.Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10);//texture); + + container.addChild(graphics); + + parent.addChild(container); + + // graphics.position.x = 100; + // graphics.position.y = 100; + container.position.x -= 100; + container.position.y -= 100; + + var bounds = graphics.getBounds(); + + expect(bounds.x).to.equal(-110); + expect(bounds.y).to.equal(-110); + expect(bounds.width).to.equal(20); + expect(bounds.height).to.equal(20); + + + }); + + it('should register correct width and height with an a Text Object', function() { + + var parent = new PIXI.Container(); + + var container = new PIXI.Container();//Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var text = new PIXI.Text('i am some text'); + + container.addChild(text); + + parent.addChild(container); + + var bounds = text.getBounds(); + var bx = bounds.width; + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.be.greaterThan(0); + expect(bounds.height).to.greaterThan(0); + + text.text = 'hello!'; + + var bounds = text.getBounds(); + + // this variable seems to be different on different devices. a font thing? + expect(bounds.width).to.not.equal(bx); + + }); + + + }); diff --git a/test/unit/core/LocalBounds.test.js b/test/unit/core/LocalBounds.test.js new file mode 100644 index 0000000..7130af6 --- /dev/null +++ b/test/unit/core/LocalBounds.test.js @@ -0,0 +1,119 @@ +describe('getLocalBounds', function () { + + + it('should register correct local-bounds with a LOADED Sprite', function() { + var parent = new PIXI.Container(); + var texture = PIXI.RenderTexture.create(10, 10); + + var sprite = new PIXI.Sprite(texture); + + parent.addChild(sprite); + + + var bounds = sprite.getLocalBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + sprite.position.x = 20; + sprite.position.y = 20; + + sprite.scale.x = 2; + sprite.scale.y = 2; + + var bounds = sprite.getLocalBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + + }); + + it('should register correct local-bounds with Graphics', function() { + var parent = new PIXI.Container(); + + var graphics = new PIXI.Graphics(); + + graphics.beginFill(0xFF0000).drawCircle(0, 0, 10);//texture); + + graphics.scale.set(2); + + parent.addChild(graphics); + + var bounds = graphics.getLocalBounds(); + + expect(bounds.x).to.equal(-10); + expect(bounds.y).to.equal(-10); + expect(bounds.width).to.equal(20); + expect(bounds.height).to.equal(20); + + }); + + it('should register correct local-bounds with an empty Container', function() { + var parent = new PIXI.Container(); + + var container = new PIXI.Container();//Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + parent.addChild(container); + + var bounds = container.getLocalBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(0); + expect(bounds.height).to.equal(0); + + + }); + + it('should register correct local-bounds with an item that has already had its parent Container transformed', function() { + var parent = new PIXI.Container(); + + var container = new PIXI.Container();//Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var graphics = new PIXI.Graphics().beginFill(0xFF0000).drawRect(0, 0, 10, 10);//texture); + + + parent.addChild(container); + container.addChild(graphics); + + container.position.x = 100; + container.position.y = 100; + + var bounds = container.getLocalBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + + + }); + + it('should register correct local-bounds with a Mesh', function() { + var parent = new PIXI.Container(); + + var texture = PIXI.RenderTexture.create(10, 10); + + var plane = new PIXI.mesh.Plane(texture); + + parent.addChild(plane); + + plane.position.x = 20; + plane.position.y = 20; + + var bounds = plane.getLocalBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + + }); + }); diff --git a/src/core/display/BoundsBuilder.js b/src/core/display/BoundsBuilder.js new file mode 100644 index 0000000..f86d208 --- /dev/null +++ b/src/core/display/BoundsBuilder.js @@ -0,0 +1,216 @@ +var math = require('../math'), + Rectangle = math.Rectangle; + +/** + * 'Builder' pattern for bounds rectangles + * Axis-Aligned Bounding Box + * It is not a shape! Its mutable thing, no 'EMPTY' or that kind of problems + * + * @class + * @memberof PIXI + */ +function BoundsBuilder() +{ + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; + + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; + + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; + + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; +} + +BoundsBuilder.prototype.constructor = BoundsBuilder; +module.exports = BoundsBuilder; + +BoundsBuilder.prototype.isEmpty = function() +{ + return this.minX > this.maxX || this.minY > this.maxY; +}; + +BoundsBuilder.prototype.clear = function() +{ + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; +}; + +/** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ +BoundsBuilder.prototype.getRectangle = function(tempRect) +{ + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + tempRect = tempRect || new Rectangle(0, 0, 1, 1); + tempRect.x = this.minX; + tempRect.y = this.minY; + tempRect.width = this.maxX - this.minX; + tempRect.height = this.maxY - this.minY; + return tempRect; +}; + +/** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ +BoundsBuilder.prototype.addPoint = function (point) +{ + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); +}; + +/** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.BoundsBuilder} + */ +BoundsBuilder.prototype.addQuad = function(vertices) +{ + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +/** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ +BoundsBuilder.prototype.addFrame = function(transform, x0, y0, x1, y1) +{ + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +/** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ +BoundsBuilder.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) +{ + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +BoundsBuilder.prototype.addBounds = function(bounds) +{ + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; +}; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 7992d77..e69807a 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -1,5 +1,4 @@ -var math = require('../math'), - utils = require('../utils'), +var utils = require('../utils'), DisplayObject = require('./DisplayObject'); /** @@ -364,6 +363,8 @@ */ Container.prototype.updateTransform = function () { + this._currentBounds = null; + if (!this.visible) { return; @@ -379,111 +380,39 @@ this.children[i].updateTransform(); } - this._currentBounds = null; + }; // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; -/** -* Retrieves the bounds of the Container as a rectangle. The bounds calculation takes all visible children into consideration. - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ -Container.prototype.getBounds = function () + +Container.prototype.calculateBounds = function () { - if(!this._currentBounds) + this._bounds_.clear(); + // if we have already done this on THIS frame. + + if(!this.visible) { - - if (this.children.length === 0) - { - return math.Rectangle.EMPTY; - } - - // TODO the bounds have already been calculated this render session so return what we have - - var minX = Infinity; - var minY = Infinity; - - var maxX = -Infinity; - var maxY = -Infinity; - - var childBounds; - var childMaxX; - var childMaxY; - - var childVisible = false; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - childBounds = this.children[i].getBounds(); - if (childBounds === math.Rectangle.EMPTY) { - continue; - } - childVisible = true; - - minX = minX < childBounds.x ? minX : childBounds.x; - minY = minY < childBounds.y ? minY : childBounds.y; - - childMaxX = childBounds.width + childBounds.x; - childMaxY = childBounds.height + childBounds.y; - - maxX = maxX > childMaxX ? maxX : childMaxX; - maxY = maxY > childMaxY ? maxY : childMaxY; - } - - if (!childVisible) - { - this._currentBounds = math.Rectangle.EMPTY; - return this._currentBounds; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.y = minY; - bounds.width = maxX - minX; - bounds.height = maxY - minY; - - this._currentBounds = bounds; + return; } - return this._currentBounds; + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds_.addBounds(child._bounds_); + } }; -Container.prototype.containerGetBounds = Container.prototype.getBounds; - -/** - * Retrieves the non-global local bounds of the Container as a rectangle. - * The calculation takes all visible children into consideration. - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ -Container.prototype.getLocalBounds = function () +Container.prototype._calculateBounds = function () { - var matrixCache = this.transform.worldTransform; - - this.transform.worldTransform = math.Matrix.IDENTITY; - this.transform._worldID++; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } - - this.transform.worldTransform = matrixCache; - this.transform._worldID++; - - this._currentBounds = null; - - return this.getBounds(); + //FILL IN// }; /** diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index ff4fda1..535f833 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,9 +3,9 @@ CONST = require('../const'), TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), + BoundsBulder = require('./BoundsBuilder'), _tempDisplayObjectParent = new DisplayObject(); - /** * The base class for all objects that are rendered on the screen. * This is an abstract class and should not be used on its own rather it should be extended. @@ -102,6 +102,8 @@ * @private */ this._mask = null; + + this._bounds_ = new BoundsBulder(); } // constructor @@ -350,15 +352,53 @@ DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; /** + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() + */ +DisplayObject.prototype._recursivePostUpdateTransform = function() +{ + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(_tempDisplayObjectParent.transform); + } +}; + +/** * * * Retrieves the bounds of the displayObject as a rectangle object * * @return {PIXI.Rectangle} the rectangular bounding area */ -DisplayObject.prototype.getBounds = function () // jshint unused:false +DisplayObject.prototype.getBounds = function (skipUpdate) { - return math.Rectangle.EMPTY; + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(!this._currentBounds) + { + this.calculateBounds(); + this._currentBounds = this._bounds_.getRectangle(this._bounds); + } + + return this._currentBounds; }; /** @@ -368,7 +408,18 @@ */ DisplayObject.prototype.getLocalBounds = function () { - return this.getBounds(math.Matrix.IDENTITY); + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = _tempDisplayObjectParent.transform; + + var bounds = this.getBounds(); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; }; /** @@ -377,24 +428,29 @@ * @param position {PIXI.Point} The world origin to calculate from * @return {PIXI.Point} A point object representing the position of this object */ -DisplayObject.prototype.toGlobal = function (position) +DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) { - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) + if(!skipUpdate) { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } } // don't need to update the lot - return this.worldTransform.apply(position); + return this.worldTransform.apply(position, point); }; /** @@ -405,25 +461,30 @@ * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) * @return {PIXI.Point} A point object representing the position of this object */ -DisplayObject.prototype.toLocal = function (position, from, point) +DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) { if (from) { - position = from.toGlobal(position); + position = from.toGlobal(position, point, skipUpdate); } - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) + if(! skipUpdate) { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } } // simply apply the matrix.. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 6c575d8..4e37d2d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -5,6 +5,7 @@ Sprite = require('../sprites/Sprite'), math = require('../math'), CONST = require('../const'), + BoundsBuilder = require('../display/BoundsBuilder'), bezierCurveTo = require('./utils/bezierCurveTo'), CanvasRenderer = require('../renderers/canvas/CanvasRenderer'), canvasRenderer, @@ -118,7 +119,7 @@ * @member {PIXI.Rectangle} * @private */ - this._localBounds = new math.Rectangle(0,0,1,1); + this._localBounds = new BoundsBuilder(); /** * Used to detect if the graphics object has changed. If this is set to true then the graphics @@ -774,87 +775,24 @@ * object's worldTransform. * @return {PIXI.Rectangle} the rectangular bounding area */ -Graphics.prototype.getBounds = function (matrix) +Graphics.prototype._calculateBounds = function () { - if(!this._currentBounds) + if (!this.renderable) { - - // return an empty object if the item is a mask! - if (!this.renderable) - { - return math.Rectangle.EMPTY; - } - - if (this.boundsDirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.boundsDirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - this._currentBounds = this._bounds; + return; } - return this._currentBounds; + if (this.boundsDirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.boundsDirty = false; + } + + var lb = this._localBounds; + this._bounds_.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); }; /** @@ -982,11 +920,11 @@ var padding = this.boundsPadding; - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; }; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 0c426a5..62b3a44 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -74,8 +74,7 @@ // for now we go off the filter of the first resolution.. var resolution = filters[0].resolution; var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(); - + var targetBounds = target.filterArea || target.getBounds(true); var sourceFrame = currentState.sourceFrame; var destinationFrame = currentState.destinationFrame; @@ -84,7 +83,16 @@ sourceFrame.width = (((targetBounds.width + padding*2) * resolution) | 0) / resolution; sourceFrame.height = (((targetBounds.height + padding*2)* resolution) | 0) / resolution; - sourceFrame.fit(filterData.stack[0].destinationFrame); + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. + + } + else + { + sourceFrame.fit(filterData.stack[0].destinationFrame); + } destinationFrame.width = sourceFrame.width; destinationFrame.height = sourceFrame.height; @@ -379,7 +387,6 @@ renderTarget.resolution = resolution; renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; }; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index f7ccc39..d4ab057 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -341,89 +341,11 @@ renderer.plugins.sprite.render(this); }; - -/** - * Returns the bounds of the Sprite as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Sprite.prototype.getBounds = function () +Sprite.prototype._calculateBounds = function () { - //TODO lookinto caching.. - if(!this._currentBounds) - { - // set the vertex data - this.calculateVertices(); - - // set the vertex data - this.calculateBoundsVertices(); - - var minX, maxX, minY, maxY, - w0, w1, h0, h1, - vertexData = this.vertexData; - - var x1 = vertexData[8]; - var y1 = vertexData[9]; - - var x2 = vertexData[10]; - var y2 = vertexData[11]; - - var x3 = vertexData[12]; - var y3 = vertexData[13]; - - var x4 = vertexData[14]; - var y4 = vertexData[15]; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - // check for children - if(this.children.length) - { - var childBounds = this.containerGetBounds(); - - w0 = childBounds.x; - w1 = childBounds.x + childBounds.width; - h0 = childBounds.y; - h1 = childBounds.y + childBounds.height; - - minX = (minX < w0) ? minX : w0; - minY = (minY < h0) ? minY : h0; - - maxX = (maxX > w1) ? maxX : w1; - maxY = (maxY > h1) ? maxY : h1; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - } - - return this._currentBounds; + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds_.addQuad(this.vertexData); }; /** diff --git a/src/core/text/Text.js b/src/core/text/Text.js index d77e08c..ee875de 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -634,16 +634,14 @@ }; /** - * Returns the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param matrix {PIXI.Matrix} the transformation matrix of the Text - * @return {PIXI.Rectangle} the framing rectangle + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. */ -Text.prototype.getBounds = function (matrix) +Text.prototype._calculateBounds = function () { this.updateText(true); - - return Sprite.prototype.getBounds.call(this, matrix); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds_.addQuad(this.vertexData); }; /** diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index f61829a..965f597 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -110,11 +110,6 @@ this.emit('update', this); - //TODO - remove this! - if(this.filterManager) - { - this.filterManager.resize(this.width, this.height); - } }; /** @@ -124,13 +119,6 @@ BaseRenderTexture.prototype.destroy = function () { BaseTexture.prototype.destroy.call(this, true); - - // destroy the filtermanager.. - if(this.filterManager) - { - this.filterManager.destroy(); - } - this.renderer = null; }; diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index f9d87ee..9a5d304 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -445,54 +445,10 @@ * @param [matrix=this.worldTransform] {PIXI.Matrix} the transformation matrix of the sprite * @return {PIXI.Rectangle} the framing rectangle */ -Mesh.prototype.getBounds = function (matrix) +Mesh.prototype._calculateBounds = function () { - if (!this._currentBounds) { - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var maxX = -Infinity; - var maxY = -Infinity; - - var minX = Infinity; - var minY = Infinity; - - var vertices = this.vertices; - for (var i = 0, n = vertices.length; i < n; i += 2) { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; - - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - } - - if (minX === -Infinity || maxY === Infinity) { - return core.Rectangle.EMPTY; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - } - - return this._currentBounds; + //TODO - we can cache local bounds and use them if they are dirty (like graphics) + this._bounds_.addVertices(this.transform, this.vertices, 0, this.vertices.length); }; /** diff --git a/test/unit/core/Bounds.test.js b/test/unit/core/Bounds.test.js new file mode 100644 index 0000000..886b75b --- /dev/null +++ b/test/unit/core/Bounds.test.js @@ -0,0 +1,327 @@ +describe('getBounds', function () { + + + it('should register correct width and height with a LOADED Sprite', function() { + var parent = new PIXI.Container(); + var texture = PIXI.RenderTexture.create(10, 10); + + var sprite = new PIXI.Sprite(texture); + + parent.addChild(sprite); + + + var bounds = sprite.getBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + sprite.position.x = 20; + sprite.position.y = 20; + + sprite.scale.x = 2; + sprite.scale.y = 2; + + var bounds = sprite.getBounds(); + + expect(bounds.x).to.equal(20); + expect(bounds.y).to.equal(20); + expect(bounds.width).to.equal(20); + expect(bounds.height).to.equal(20); + + + var bounds = sprite.getBounds(true); + + expect(bounds.x).to.equal(20); + expect(bounds.y).to.equal(20); + expect(bounds.width).to.equal(20); + expect(bounds.height).to.equal(20); + + }); + + it('should register correct width and height with Graphics', function() { + var parent = new PIXI.Container(); + + var graphics = new PIXI.Graphics(); + + var bounds = graphics.getBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(0); + expect(bounds.height).to.equal(0); + + graphics.beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + parent.addChild(graphics); + + var bounds = graphics.getBounds(); + + expect(bounds.x).to.equal(-10); + expect(bounds.y).to.equal(-10); + expect(bounds.width).to.equal(20); + expect(bounds.height).to.equal(20); + + graphics.position.x = 20; + graphics.position.y = 20; + + graphics.scale.x = 2; + graphics.scale.y = 2; + + var bounds = graphics.getBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(40); + expect(bounds.height).to.equal(40); + + var bounds = graphics.getBounds(true); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(40); + expect(bounds.height).to.equal(40); + + }); + + it('should register correct width and height with an empty Container', function() { + var parent = new PIXI.Container(); + + var container = new PIXI.Container();//Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + parent.addChild(container); + + var bounds = container.getBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(0); + expect(bounds.height).to.equal(0); + + container.position.x = 20; + container.position.y = 20; + + container.scale.x = 2; + container.scale.y = 2; + + var bounds = container.getBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(0); + expect(bounds.height).to.equal(0); + + }); + + it('should register correct width and height with a Container', function() { + var parent = new PIXI.Container(); + + var container = new PIXI.Container();//Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var graphics = new PIXI.Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var texture = PIXI.RenderTexture.create(10, 10); + var sprite = new PIXI.Sprite(texture); + + container.addChild(sprite); + container.addChild(graphics); + + parent.addChild(container); + + sprite.position.x = 30; + sprite.position.y = 20; + graphics.position.x = 100; + graphics.position.y = 100; + + var bounds = container.getBounds(); + + expect(bounds.x).to.equal(30); + expect(bounds.y).to.equal(20); + expect(bounds.width).to.equal(80); + expect(bounds.height).to.equal(90); + + container.rotation = 0.1; + + var bounds = container.getBounds(); + + expect(bounds.x | 0).to.equal(26); + expect(bounds.y | 0).to.equal(22); + expect(bounds.width | 0).to.equal(73); + expect(bounds.height | 0).to.equal(97); + + var bounds = container.getBounds(true); + + expect(bounds.x | 0).to.equal(26); + expect(bounds.y | 0).to.equal(22); + expect(bounds.width | 0).to.equal(73); + expect(bounds.height | 0).to.equal(97); + + + }); + + it('should register correct width and height with an item that has already had its parent Container transformed', function() { + var parent = new PIXI.Container(); + + var container = new PIXI.Container();//Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var graphics = new PIXI.Graphics().beginFill(0xFF0000).drawRect(0, 0, 10, 10);//texture); + + + parent.addChild(container); + container.addChild(graphics); + + container.position.x = 100; + container.position.y = 100; + + var bounds = container.getBounds(); + + expect(bounds.x).to.equal(100); + expect(bounds.y).to.equal(100); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + + var bounds = graphics.getBounds(true); + + expect(bounds.x).to.equal(100); + expect(bounds.y).to.equal(100); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + + + + }); + + it('should register correct width and height with a Mesh', function() { + var parent = new PIXI.Container(); + + var texture = PIXI.RenderTexture.create(10, 10); + + var plane = new PIXI.mesh.Plane(texture); + + parent.addChild(plane); + + plane.position.x = 20; + plane.position.y = 20; + + var bounds = plane.getBounds(); + + expect(bounds.x).to.equal(20); + expect(bounds.y).to.equal(20); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + plane.scale.x = 2; + plane.scale.y = 2; + + var bounds = plane.getBounds(); + + expect(bounds.x).to.equal(20); + expect(bounds.y).to.equal(20); + expect(bounds.width).to.equal(20); + expect(bounds.height).to.equal(20); + + }); + + it('should register correct width and height with an a DisplayObject is visible false', function() { + + var parent = new PIXI.Container(); + + var container = new PIXI.Container();//Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var graphics = new PIXI.Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var texture = PIXI.RenderTexture.create(10, 10); + var sprite = new PIXI.Sprite(texture); + + container.addChild(sprite); + container.addChild(graphics); + + parent.addChild(container); + + sprite.position.x = 30; + sprite.position.y = 20; + graphics.position.x = 100; + graphics.position.y = 100; + + graphics.visible = false; + + var bounds = container.getBounds(); + + expect(bounds.x).to.equal(30); + expect(bounds.y).to.equal(20); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + container.visible = false; + + var bounds = container.getBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(0); + expect(bounds.height).to.equal(0); + + + }); + + it('should register correct width and height with an a DisplayObject parent has moved', function() { + + var parent = new PIXI.Container(); + + var container = new PIXI.Container();//Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var graphics = new PIXI.Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10);//texture); + + container.addChild(graphics); + + parent.addChild(container); + + // graphics.position.x = 100; + // graphics.position.y = 100; + container.position.x -= 100; + container.position.y -= 100; + + var bounds = graphics.getBounds(); + + expect(bounds.x).to.equal(-110); + expect(bounds.y).to.equal(-110); + expect(bounds.width).to.equal(20); + expect(bounds.height).to.equal(20); + + + }); + + it('should register correct width and height with an a Text Object', function() { + + var parent = new PIXI.Container(); + + var container = new PIXI.Container();//Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var text = new PIXI.Text('i am some text'); + + container.addChild(text); + + parent.addChild(container); + + var bounds = text.getBounds(); + var bx = bounds.width; + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.be.greaterThan(0); + expect(bounds.height).to.greaterThan(0); + + text.text = 'hello!'; + + var bounds = text.getBounds(); + + // this variable seems to be different on different devices. a font thing? + expect(bounds.width).to.not.equal(bx); + + }); + + + }); diff --git a/test/unit/core/LocalBounds.test.js b/test/unit/core/LocalBounds.test.js new file mode 100644 index 0000000..7130af6 --- /dev/null +++ b/test/unit/core/LocalBounds.test.js @@ -0,0 +1,119 @@ +describe('getLocalBounds', function () { + + + it('should register correct local-bounds with a LOADED Sprite', function() { + var parent = new PIXI.Container(); + var texture = PIXI.RenderTexture.create(10, 10); + + var sprite = new PIXI.Sprite(texture); + + parent.addChild(sprite); + + + var bounds = sprite.getLocalBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + sprite.position.x = 20; + sprite.position.y = 20; + + sprite.scale.x = 2; + sprite.scale.y = 2; + + var bounds = sprite.getLocalBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + + }); + + it('should register correct local-bounds with Graphics', function() { + var parent = new PIXI.Container(); + + var graphics = new PIXI.Graphics(); + + graphics.beginFill(0xFF0000).drawCircle(0, 0, 10);//texture); + + graphics.scale.set(2); + + parent.addChild(graphics); + + var bounds = graphics.getLocalBounds(); + + expect(bounds.x).to.equal(-10); + expect(bounds.y).to.equal(-10); + expect(bounds.width).to.equal(20); + expect(bounds.height).to.equal(20); + + }); + + it('should register correct local-bounds with an empty Container', function() { + var parent = new PIXI.Container(); + + var container = new PIXI.Container();//Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + parent.addChild(container); + + var bounds = container.getLocalBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(0); + expect(bounds.height).to.equal(0); + + + }); + + it('should register correct local-bounds with an item that has already had its parent Container transformed', function() { + var parent = new PIXI.Container(); + + var container = new PIXI.Container();//Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var graphics = new PIXI.Graphics().beginFill(0xFF0000).drawRect(0, 0, 10, 10);//texture); + + + parent.addChild(container); + container.addChild(graphics); + + container.position.x = 100; + container.position.y = 100; + + var bounds = container.getLocalBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + + + }); + + it('should register correct local-bounds with a Mesh', function() { + var parent = new PIXI.Container(); + + var texture = PIXI.RenderTexture.create(10, 10); + + var plane = new PIXI.mesh.Plane(texture); + + parent.addChild(plane); + + plane.position.x = 20; + plane.position.y = 20; + + var bounds = plane.getLocalBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + + }); + }); diff --git a/test/unit/core/toGlobal.test.js b/test/unit/core/toGlobal.test.js new file mode 100644 index 0000000..7e28e00 --- /dev/null +++ b/test/unit/core/toGlobal.test.js @@ -0,0 +1,32 @@ +describe('toGlobal', function () { + + + it('should return correct global cordinates of a point from within a displayObject', function() { + + var parent = new PIXI.Container(); + + var container = new PIXI.Container(); + + parent.addChild(container); + + var point = new PIXI.Point(100, 100); + + var globalPoint = container.toGlobal(point); + + expect(globalPoint.x).to.equal(100); + expect(globalPoint.y).to.equal(100); + + container.position.x = 20; + container.position.y = 20; + + container.scale.x = 2; + container.scale.y = 2; + + var globalPoint = container.toGlobal(point); + + expect(globalPoint.x).to.equal(220); + expect(globalPoint.y).to.equal(220); + + }); + +}); diff --git a/src/core/display/BoundsBuilder.js b/src/core/display/BoundsBuilder.js new file mode 100644 index 0000000..f86d208 --- /dev/null +++ b/src/core/display/BoundsBuilder.js @@ -0,0 +1,216 @@ +var math = require('../math'), + Rectangle = math.Rectangle; + +/** + * 'Builder' pattern for bounds rectangles + * Axis-Aligned Bounding Box + * It is not a shape! Its mutable thing, no 'EMPTY' or that kind of problems + * + * @class + * @memberof PIXI + */ +function BoundsBuilder() +{ + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; + + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; + + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; + + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; +} + +BoundsBuilder.prototype.constructor = BoundsBuilder; +module.exports = BoundsBuilder; + +BoundsBuilder.prototype.isEmpty = function() +{ + return this.minX > this.maxX || this.minY > this.maxY; +}; + +BoundsBuilder.prototype.clear = function() +{ + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; +}; + +/** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ +BoundsBuilder.prototype.getRectangle = function(tempRect) +{ + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + tempRect = tempRect || new Rectangle(0, 0, 1, 1); + tempRect.x = this.minX; + tempRect.y = this.minY; + tempRect.width = this.maxX - this.minX; + tempRect.height = this.maxY - this.minY; + return tempRect; +}; + +/** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ +BoundsBuilder.prototype.addPoint = function (point) +{ + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); +}; + +/** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.BoundsBuilder} + */ +BoundsBuilder.prototype.addQuad = function(vertices) +{ + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +/** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ +BoundsBuilder.prototype.addFrame = function(transform, x0, y0, x1, y1) +{ + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +/** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ +BoundsBuilder.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) +{ + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; +}; + +BoundsBuilder.prototype.addBounds = function(bounds) +{ + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; +}; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 7992d77..e69807a 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -1,5 +1,4 @@ -var math = require('../math'), - utils = require('../utils'), +var utils = require('../utils'), DisplayObject = require('./DisplayObject'); /** @@ -364,6 +363,8 @@ */ Container.prototype.updateTransform = function () { + this._currentBounds = null; + if (!this.visible) { return; @@ -379,111 +380,39 @@ this.children[i].updateTransform(); } - this._currentBounds = null; + }; // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; -/** -* Retrieves the bounds of the Container as a rectangle. The bounds calculation takes all visible children into consideration. - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ -Container.prototype.getBounds = function () + +Container.prototype.calculateBounds = function () { - if(!this._currentBounds) + this._bounds_.clear(); + // if we have already done this on THIS frame. + + if(!this.visible) { - - if (this.children.length === 0) - { - return math.Rectangle.EMPTY; - } - - // TODO the bounds have already been calculated this render session so return what we have - - var minX = Infinity; - var minY = Infinity; - - var maxX = -Infinity; - var maxY = -Infinity; - - var childBounds; - var childMaxX; - var childMaxY; - - var childVisible = false; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - childBounds = this.children[i].getBounds(); - if (childBounds === math.Rectangle.EMPTY) { - continue; - } - childVisible = true; - - minX = minX < childBounds.x ? minX : childBounds.x; - minY = minY < childBounds.y ? minY : childBounds.y; - - childMaxX = childBounds.width + childBounds.x; - childMaxY = childBounds.height + childBounds.y; - - maxX = maxX > childMaxX ? maxX : childMaxX; - maxY = maxY > childMaxY ? maxY : childMaxY; - } - - if (!childVisible) - { - this._currentBounds = math.Rectangle.EMPTY; - return this._currentBounds; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.y = minY; - bounds.width = maxX - minX; - bounds.height = maxY - minY; - - this._currentBounds = bounds; + return; } - return this._currentBounds; + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds_.addBounds(child._bounds_); + } }; -Container.prototype.containerGetBounds = Container.prototype.getBounds; - -/** - * Retrieves the non-global local bounds of the Container as a rectangle. - * The calculation takes all visible children into consideration. - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ -Container.prototype.getLocalBounds = function () +Container.prototype._calculateBounds = function () { - var matrixCache = this.transform.worldTransform; - - this.transform.worldTransform = math.Matrix.IDENTITY; - this.transform._worldID++; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } - - this.transform.worldTransform = matrixCache; - this.transform._worldID++; - - this._currentBounds = null; - - return this.getBounds(); + //FILL IN// }; /** diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index ff4fda1..535f833 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,9 +3,9 @@ CONST = require('../const'), TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), + BoundsBulder = require('./BoundsBuilder'), _tempDisplayObjectParent = new DisplayObject(); - /** * The base class for all objects that are rendered on the screen. * This is an abstract class and should not be used on its own rather it should be extended. @@ -102,6 +102,8 @@ * @private */ this._mask = null; + + this._bounds_ = new BoundsBulder(); } // constructor @@ -350,15 +352,53 @@ DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; /** + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() + */ +DisplayObject.prototype._recursivePostUpdateTransform = function() +{ + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(_tempDisplayObjectParent.transform); + } +}; + +/** * * * Retrieves the bounds of the displayObject as a rectangle object * * @return {PIXI.Rectangle} the rectangular bounding area */ -DisplayObject.prototype.getBounds = function () // jshint unused:false +DisplayObject.prototype.getBounds = function (skipUpdate) { - return math.Rectangle.EMPTY; + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(!this._currentBounds) + { + this.calculateBounds(); + this._currentBounds = this._bounds_.getRectangle(this._bounds); + } + + return this._currentBounds; }; /** @@ -368,7 +408,18 @@ */ DisplayObject.prototype.getLocalBounds = function () { - return this.getBounds(math.Matrix.IDENTITY); + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = _tempDisplayObjectParent.transform; + + var bounds = this.getBounds(); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; }; /** @@ -377,24 +428,29 @@ * @param position {PIXI.Point} The world origin to calculate from * @return {PIXI.Point} A point object representing the position of this object */ -DisplayObject.prototype.toGlobal = function (position) +DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) { - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) + if(!skipUpdate) { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } } // don't need to update the lot - return this.worldTransform.apply(position); + return this.worldTransform.apply(position, point); }; /** @@ -405,25 +461,30 @@ * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) * @return {PIXI.Point} A point object representing the position of this object */ -DisplayObject.prototype.toLocal = function (position, from, point) +DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) { if (from) { - position = from.toGlobal(position); + position = from.toGlobal(position, point, skipUpdate); } - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) + if(! skipUpdate) { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = _tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } } // simply apply the matrix.. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 6c575d8..4e37d2d 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -5,6 +5,7 @@ Sprite = require('../sprites/Sprite'), math = require('../math'), CONST = require('../const'), + BoundsBuilder = require('../display/BoundsBuilder'), bezierCurveTo = require('./utils/bezierCurveTo'), CanvasRenderer = require('../renderers/canvas/CanvasRenderer'), canvasRenderer, @@ -118,7 +119,7 @@ * @member {PIXI.Rectangle} * @private */ - this._localBounds = new math.Rectangle(0,0,1,1); + this._localBounds = new BoundsBuilder(); /** * Used to detect if the graphics object has changed. If this is set to true then the graphics @@ -774,87 +775,24 @@ * object's worldTransform. * @return {PIXI.Rectangle} the rectangular bounding area */ -Graphics.prototype.getBounds = function (matrix) +Graphics.prototype._calculateBounds = function () { - if(!this._currentBounds) + if (!this.renderable) { - - // return an empty object if the item is a mask! - if (!this.renderable) - { - return math.Rectangle.EMPTY; - } - - if (this.boundsDirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.boundsDirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - this._currentBounds = this._bounds; + return; } - return this._currentBounds; + if (this.boundsDirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.boundsDirty = false; + } + + var lb = this._localBounds; + this._bounds_.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); }; /** @@ -982,11 +920,11 @@ var padding = this.boundsPadding; - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; }; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 0c426a5..62b3a44 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -74,8 +74,7 @@ // for now we go off the filter of the first resolution.. var resolution = filters[0].resolution; var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(); - + var targetBounds = target.filterArea || target.getBounds(true); var sourceFrame = currentState.sourceFrame; var destinationFrame = currentState.destinationFrame; @@ -84,7 +83,16 @@ sourceFrame.width = (((targetBounds.width + padding*2) * resolution) | 0) / resolution; sourceFrame.height = (((targetBounds.height + padding*2)* resolution) | 0) / resolution; - sourceFrame.fit(filterData.stack[0].destinationFrame); + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. + + } + else + { + sourceFrame.fit(filterData.stack[0].destinationFrame); + } destinationFrame.width = sourceFrame.width; destinationFrame.height = sourceFrame.height; @@ -379,7 +387,6 @@ renderTarget.resolution = resolution; renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; }; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index f7ccc39..d4ab057 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -341,89 +341,11 @@ renderer.plugins.sprite.render(this); }; - -/** - * Returns the bounds of the Sprite as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Sprite.prototype.getBounds = function () +Sprite.prototype._calculateBounds = function () { - //TODO lookinto caching.. - if(!this._currentBounds) - { - // set the vertex data - this.calculateVertices(); - - // set the vertex data - this.calculateBoundsVertices(); - - var minX, maxX, minY, maxY, - w0, w1, h0, h1, - vertexData = this.vertexData; - - var x1 = vertexData[8]; - var y1 = vertexData[9]; - - var x2 = vertexData[10]; - var y2 = vertexData[11]; - - var x3 = vertexData[12]; - var y3 = vertexData[13]; - - var x4 = vertexData[14]; - var y4 = vertexData[15]; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - // check for children - if(this.children.length) - { - var childBounds = this.containerGetBounds(); - - w0 = childBounds.x; - w1 = childBounds.x + childBounds.width; - h0 = childBounds.y; - h1 = childBounds.y + childBounds.height; - - minX = (minX < w0) ? minX : w0; - minY = (minY < h0) ? minY : h0; - - maxX = (maxX > w1) ? maxX : w1; - maxY = (maxY > h1) ? maxY : h1; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - } - - return this._currentBounds; + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds_.addQuad(this.vertexData); }; /** diff --git a/src/core/text/Text.js b/src/core/text/Text.js index d77e08c..ee875de 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -634,16 +634,14 @@ }; /** - * Returns the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param matrix {PIXI.Matrix} the transformation matrix of the Text - * @return {PIXI.Rectangle} the framing rectangle + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. */ -Text.prototype.getBounds = function (matrix) +Text.prototype._calculateBounds = function () { this.updateText(true); - - return Sprite.prototype.getBounds.call(this, matrix); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds_.addQuad(this.vertexData); }; /** diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index f61829a..965f597 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -110,11 +110,6 @@ this.emit('update', this); - //TODO - remove this! - if(this.filterManager) - { - this.filterManager.resize(this.width, this.height); - } }; /** @@ -124,13 +119,6 @@ BaseRenderTexture.prototype.destroy = function () { BaseTexture.prototype.destroy.call(this, true); - - // destroy the filtermanager.. - if(this.filterManager) - { - this.filterManager.destroy(); - } - this.renderer = null; }; diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index f9d87ee..9a5d304 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -445,54 +445,10 @@ * @param [matrix=this.worldTransform] {PIXI.Matrix} the transformation matrix of the sprite * @return {PIXI.Rectangle} the framing rectangle */ -Mesh.prototype.getBounds = function (matrix) +Mesh.prototype._calculateBounds = function () { - if (!this._currentBounds) { - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var maxX = -Infinity; - var maxY = -Infinity; - - var minX = Infinity; - var minY = Infinity; - - var vertices = this.vertices; - for (var i = 0, n = vertices.length; i < n; i += 2) { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; - - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - } - - if (minX === -Infinity || maxY === Infinity) { - return core.Rectangle.EMPTY; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - } - - return this._currentBounds; + //TODO - we can cache local bounds and use them if they are dirty (like graphics) + this._bounds_.addVertices(this.transform, this.vertices, 0, this.vertices.length); }; /** diff --git a/test/unit/core/Bounds.test.js b/test/unit/core/Bounds.test.js new file mode 100644 index 0000000..886b75b --- /dev/null +++ b/test/unit/core/Bounds.test.js @@ -0,0 +1,327 @@ +describe('getBounds', function () { + + + it('should register correct width and height with a LOADED Sprite', function() { + var parent = new PIXI.Container(); + var texture = PIXI.RenderTexture.create(10, 10); + + var sprite = new PIXI.Sprite(texture); + + parent.addChild(sprite); + + + var bounds = sprite.getBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + sprite.position.x = 20; + sprite.position.y = 20; + + sprite.scale.x = 2; + sprite.scale.y = 2; + + var bounds = sprite.getBounds(); + + expect(bounds.x).to.equal(20); + expect(bounds.y).to.equal(20); + expect(bounds.width).to.equal(20); + expect(bounds.height).to.equal(20); + + + var bounds = sprite.getBounds(true); + + expect(bounds.x).to.equal(20); + expect(bounds.y).to.equal(20); + expect(bounds.width).to.equal(20); + expect(bounds.height).to.equal(20); + + }); + + it('should register correct width and height with Graphics', function() { + var parent = new PIXI.Container(); + + var graphics = new PIXI.Graphics(); + + var bounds = graphics.getBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(0); + expect(bounds.height).to.equal(0); + + graphics.beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + parent.addChild(graphics); + + var bounds = graphics.getBounds(); + + expect(bounds.x).to.equal(-10); + expect(bounds.y).to.equal(-10); + expect(bounds.width).to.equal(20); + expect(bounds.height).to.equal(20); + + graphics.position.x = 20; + graphics.position.y = 20; + + graphics.scale.x = 2; + graphics.scale.y = 2; + + var bounds = graphics.getBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(40); + expect(bounds.height).to.equal(40); + + var bounds = graphics.getBounds(true); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(40); + expect(bounds.height).to.equal(40); + + }); + + it('should register correct width and height with an empty Container', function() { + var parent = new PIXI.Container(); + + var container = new PIXI.Container();//Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + parent.addChild(container); + + var bounds = container.getBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(0); + expect(bounds.height).to.equal(0); + + container.position.x = 20; + container.position.y = 20; + + container.scale.x = 2; + container.scale.y = 2; + + var bounds = container.getBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(0); + expect(bounds.height).to.equal(0); + + }); + + it('should register correct width and height with a Container', function() { + var parent = new PIXI.Container(); + + var container = new PIXI.Container();//Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var graphics = new PIXI.Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var texture = PIXI.RenderTexture.create(10, 10); + var sprite = new PIXI.Sprite(texture); + + container.addChild(sprite); + container.addChild(graphics); + + parent.addChild(container); + + sprite.position.x = 30; + sprite.position.y = 20; + graphics.position.x = 100; + graphics.position.y = 100; + + var bounds = container.getBounds(); + + expect(bounds.x).to.equal(30); + expect(bounds.y).to.equal(20); + expect(bounds.width).to.equal(80); + expect(bounds.height).to.equal(90); + + container.rotation = 0.1; + + var bounds = container.getBounds(); + + expect(bounds.x | 0).to.equal(26); + expect(bounds.y | 0).to.equal(22); + expect(bounds.width | 0).to.equal(73); + expect(bounds.height | 0).to.equal(97); + + var bounds = container.getBounds(true); + + expect(bounds.x | 0).to.equal(26); + expect(bounds.y | 0).to.equal(22); + expect(bounds.width | 0).to.equal(73); + expect(bounds.height | 0).to.equal(97); + + + }); + + it('should register correct width and height with an item that has already had its parent Container transformed', function() { + var parent = new PIXI.Container(); + + var container = new PIXI.Container();//Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var graphics = new PIXI.Graphics().beginFill(0xFF0000).drawRect(0, 0, 10, 10);//texture); + + + parent.addChild(container); + container.addChild(graphics); + + container.position.x = 100; + container.position.y = 100; + + var bounds = container.getBounds(); + + expect(bounds.x).to.equal(100); + expect(bounds.y).to.equal(100); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + + var bounds = graphics.getBounds(true); + + expect(bounds.x).to.equal(100); + expect(bounds.y).to.equal(100); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + + + + }); + + it('should register correct width and height with a Mesh', function() { + var parent = new PIXI.Container(); + + var texture = PIXI.RenderTexture.create(10, 10); + + var plane = new PIXI.mesh.Plane(texture); + + parent.addChild(plane); + + plane.position.x = 20; + plane.position.y = 20; + + var bounds = plane.getBounds(); + + expect(bounds.x).to.equal(20); + expect(bounds.y).to.equal(20); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + plane.scale.x = 2; + plane.scale.y = 2; + + var bounds = plane.getBounds(); + + expect(bounds.x).to.equal(20); + expect(bounds.y).to.equal(20); + expect(bounds.width).to.equal(20); + expect(bounds.height).to.equal(20); + + }); + + it('should register correct width and height with an a DisplayObject is visible false', function() { + + var parent = new PIXI.Container(); + + var container = new PIXI.Container();//Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var graphics = new PIXI.Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var texture = PIXI.RenderTexture.create(10, 10); + var sprite = new PIXI.Sprite(texture); + + container.addChild(sprite); + container.addChild(graphics); + + parent.addChild(container); + + sprite.position.x = 30; + sprite.position.y = 20; + graphics.position.x = 100; + graphics.position.y = 100; + + graphics.visible = false; + + var bounds = container.getBounds(); + + expect(bounds.x).to.equal(30); + expect(bounds.y).to.equal(20); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + container.visible = false; + + var bounds = container.getBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(0); + expect(bounds.height).to.equal(0); + + + }); + + it('should register correct width and height with an a DisplayObject parent has moved', function() { + + var parent = new PIXI.Container(); + + var container = new PIXI.Container();//Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var graphics = new PIXI.Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10);//texture); + + container.addChild(graphics); + + parent.addChild(container); + + // graphics.position.x = 100; + // graphics.position.y = 100; + container.position.x -= 100; + container.position.y -= 100; + + var bounds = graphics.getBounds(); + + expect(bounds.x).to.equal(-110); + expect(bounds.y).to.equal(-110); + expect(bounds.width).to.equal(20); + expect(bounds.height).to.equal(20); + + + }); + + it('should register correct width and height with an a Text Object', function() { + + var parent = new PIXI.Container(); + + var container = new PIXI.Container();//Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var text = new PIXI.Text('i am some text'); + + container.addChild(text); + + parent.addChild(container); + + var bounds = text.getBounds(); + var bx = bounds.width; + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.be.greaterThan(0); + expect(bounds.height).to.greaterThan(0); + + text.text = 'hello!'; + + var bounds = text.getBounds(); + + // this variable seems to be different on different devices. a font thing? + expect(bounds.width).to.not.equal(bx); + + }); + + + }); diff --git a/test/unit/core/LocalBounds.test.js b/test/unit/core/LocalBounds.test.js new file mode 100644 index 0000000..7130af6 --- /dev/null +++ b/test/unit/core/LocalBounds.test.js @@ -0,0 +1,119 @@ +describe('getLocalBounds', function () { + + + it('should register correct local-bounds with a LOADED Sprite', function() { + var parent = new PIXI.Container(); + var texture = PIXI.RenderTexture.create(10, 10); + + var sprite = new PIXI.Sprite(texture); + + parent.addChild(sprite); + + + var bounds = sprite.getLocalBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + sprite.position.x = 20; + sprite.position.y = 20; + + sprite.scale.x = 2; + sprite.scale.y = 2; + + var bounds = sprite.getLocalBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + + }); + + it('should register correct local-bounds with Graphics', function() { + var parent = new PIXI.Container(); + + var graphics = new PIXI.Graphics(); + + graphics.beginFill(0xFF0000).drawCircle(0, 0, 10);//texture); + + graphics.scale.set(2); + + parent.addChild(graphics); + + var bounds = graphics.getLocalBounds(); + + expect(bounds.x).to.equal(-10); + expect(bounds.y).to.equal(-10); + expect(bounds.width).to.equal(20); + expect(bounds.height).to.equal(20); + + }); + + it('should register correct local-bounds with an empty Container', function() { + var parent = new PIXI.Container(); + + var container = new PIXI.Container();//Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + parent.addChild(container); + + var bounds = container.getLocalBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(0); + expect(bounds.height).to.equal(0); + + + }); + + it('should register correct local-bounds with an item that has already had its parent Container transformed', function() { + var parent = new PIXI.Container(); + + var container = new PIXI.Container();//Graphics().beginFill(0xFF0000).drawCircle(0, 0, 10, 10);//texture); + + var graphics = new PIXI.Graphics().beginFill(0xFF0000).drawRect(0, 0, 10, 10);//texture); + + + parent.addChild(container); + container.addChild(graphics); + + container.position.x = 100; + container.position.y = 100; + + var bounds = container.getLocalBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + + + }); + + it('should register correct local-bounds with a Mesh', function() { + var parent = new PIXI.Container(); + + var texture = PIXI.RenderTexture.create(10, 10); + + var plane = new PIXI.mesh.Plane(texture); + + parent.addChild(plane); + + plane.position.x = 20; + plane.position.y = 20; + + var bounds = plane.getLocalBounds(); + + expect(bounds.x).to.equal(0); + expect(bounds.y).to.equal(0); + expect(bounds.width).to.equal(10); + expect(bounds.height).to.equal(10); + + + }); + }); diff --git a/test/unit/core/toGlobal.test.js b/test/unit/core/toGlobal.test.js new file mode 100644 index 0000000..7e28e00 --- /dev/null +++ b/test/unit/core/toGlobal.test.js @@ -0,0 +1,32 @@ +describe('toGlobal', function () { + + + it('should return correct global cordinates of a point from within a displayObject', function() { + + var parent = new PIXI.Container(); + + var container = new PIXI.Container(); + + parent.addChild(container); + + var point = new PIXI.Point(100, 100); + + var globalPoint = container.toGlobal(point); + + expect(globalPoint.x).to.equal(100); + expect(globalPoint.y).to.equal(100); + + container.position.x = 20; + container.position.y = 20; + + container.scale.x = 2; + container.scale.y = 2; + + var globalPoint = container.toGlobal(point); + + expect(globalPoint.x).to.equal(220); + expect(globalPoint.y).to.equal(220); + + }); + +}); diff --git a/test/unit/core/toLocal.test.js b/test/unit/core/toLocal.test.js new file mode 100644 index 0000000..93d6046 --- /dev/null +++ b/test/unit/core/toLocal.test.js @@ -0,0 +1,59 @@ +describe('toLocal', function () { + + + it('should return correct local cordinates of a displayObject', function() { + + var parent = new PIXI.Container(); + + var container = new PIXI.Container(); + + parent.addChild(container); + + var point = new PIXI.Point(100, 100); + + var localPoint = container.toLocal(point); + + expect(localPoint.x).to.equal(100); + expect(localPoint.y).to.equal(100); + + container.position.x = 20; + container.position.y = 20; + + container.scale.x = 2; + container.scale.y = 2; + + var localPoint = container.toLocal(point); + + expect(localPoint.x).to.equal(40); + expect(localPoint.y).to.equal(40); + + }); + + it('should map the correct local cordinates of a displayObject to another', function() { + + var parent = new PIXI.Container(); + + var container = new PIXI.Container(); + var container2 = new PIXI.Container(); + + parent.addChild(container); + parent.addChild(container2); + + container2.position.x = 100; + container2.position.y = 100; + + var point = new PIXI.Point(100, 100); + + // container.position.x = 20; + // container.position.y = 20; + + container.scale.x = 2; + container.scale.y = 2; + + var localPoint = container.toLocal(point, container2); + + expect(localPoint.x).to.equal(100); + expect(localPoint.y).to.equal(100); + + }); +});