diff --git a/src/core/display/ObservablePoint.js b/src/core/display/ObservablePoint.js index fd7a04b..958661f 100644 --- a/src/core/display/ObservablePoint.js +++ b/src/core/display/ObservablePoint.js @@ -35,7 +35,7 @@ set: function (value) { this._x = value; - this.transform.dirty = true; + this.transform._versionLocal++; } }, @@ -47,7 +47,7 @@ set: function (value) { this._y = value; - this.transform.dirty = true; + this.transform._versionLocal++; } } }); @@ -63,6 +63,6 @@ { this._x = x || 0; this._y = y || ( (y !== 0) ? this._x : 0 ); - - this.transform.dirty = true; + + this.transform._versionLocal++; }; diff --git a/src/core/display/ObservablePoint.js b/src/core/display/ObservablePoint.js index fd7a04b..958661f 100644 --- a/src/core/display/ObservablePoint.js +++ b/src/core/display/ObservablePoint.js @@ -35,7 +35,7 @@ set: function (value) { this._x = value; - this.transform.dirty = true; + this.transform._versionLocal++; } }, @@ -47,7 +47,7 @@ set: function (value) { this._y = value; - this.transform.dirty = true; + this.transform._versionLocal++; } } }); @@ -63,6 +63,6 @@ { this._x = x || 0; this._y = y || ( (y !== 0) ? this._x : 0 ); - - this.transform.dirty = true; + + this.transform._versionLocal++; }; diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index 61161d0..b86aa2d 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -1,6 +1,7 @@ var math = require('../math'); var ObservablePoint = require('./ObservablePoint'); +var generatorId = 0; /** * The Point object represents a location in a two-dimensional coordinate system, where x represents * the horizontal axis and y represents the vertical axis. @@ -19,13 +20,17 @@ this.scale = new ObservablePoint(this,1, 1); this.pivot = new ObservablePoint(this, 0, 0); this.skew = new ObservablePoint(this, 0,0); - + this.rotation = 0; this._sr = Math.sin(0); this._cr = Math.cos(0); - - this.dirty = true; - this.updated = true; + + this._versionLocal = 0; + this._versionGlobal = 0; + this._dirtyLocal = 0; + this._dirtyParentVersion = -1; + this._dirtyParentId = -1; + this._transformId = ++generatorId; } TransformStatic.prototype.constructor = TransformStatic; @@ -36,21 +41,21 @@ var wt = this.worldTransform; var lt = this.localTransform; - if(this.dirty) + if(this._dirtyLocal !== this._versionLocal || + parentTransform._dirtyParentId !== parentTransform._transformId || + parentTransform._dirtyParentVersion !== parentTransform._versionGlobal ) { - // get the matrix values of the displayobject based on its transform properties.. - lt.a = this._cr * this.scale._x; - lt.b = this._sr * this.scale._x; - lt.c = -this._sr * this.scale._y; - lt.d = this._cr * this.scale._y; - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - } - - this.updated = this.dirty || parentTransform.updated; - - if(this.updated) - { + if(this._dirtyLocal !== this._versionLocal) + { + // get the matrix values of the displayobject based on its transform properties.. + lt.a = this._cr * this.scale._x; + lt.b = this._sr * this.scale._x; + lt.c = -this._sr * this.scale._y; + lt.d = this._cr * this.scale._y; + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._dirtyLocal = this._versionLocal; + } // concat the parent matrix with the objects transform. wt.a = lt.a * pt.a + lt.b * pt.c; wt.b = lt.a * pt.b + lt.b * pt.d; @@ -58,10 +63,12 @@ wt.d = lt.c * pt.b + lt.d * pt.d; wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._dirtyParentId = parentTransform._transformId; + this._dirtyParentVersion = parentTransform._versionGlobal; + this._versionGlobal++; } - - this.dirty = false; -} +}; module.exports = TransformStatic; diff --git a/src/core/display/ObservablePoint.js b/src/core/display/ObservablePoint.js index fd7a04b..958661f 100644 --- a/src/core/display/ObservablePoint.js +++ b/src/core/display/ObservablePoint.js @@ -35,7 +35,7 @@ set: function (value) { this._x = value; - this.transform.dirty = true; + this.transform._versionLocal++; } }, @@ -47,7 +47,7 @@ set: function (value) { this._y = value; - this.transform.dirty = true; + this.transform._versionLocal++; } } }); @@ -63,6 +63,6 @@ { this._x = x || 0; this._y = y || ( (y !== 0) ? this._x : 0 ); - - this.transform.dirty = true; + + this.transform._versionLocal++; }; diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index 61161d0..b86aa2d 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -1,6 +1,7 @@ var math = require('../math'); var ObservablePoint = require('./ObservablePoint'); +var generatorId = 0; /** * The Point object represents a location in a two-dimensional coordinate system, where x represents * the horizontal axis and y represents the vertical axis. @@ -19,13 +20,17 @@ this.scale = new ObservablePoint(this,1, 1); this.pivot = new ObservablePoint(this, 0, 0); this.skew = new ObservablePoint(this, 0,0); - + this.rotation = 0; this._sr = Math.sin(0); this._cr = Math.cos(0); - - this.dirty = true; - this.updated = true; + + this._versionLocal = 0; + this._versionGlobal = 0; + this._dirtyLocal = 0; + this._dirtyParentVersion = -1; + this._dirtyParentId = -1; + this._transformId = ++generatorId; } TransformStatic.prototype.constructor = TransformStatic; @@ -36,21 +41,21 @@ var wt = this.worldTransform; var lt = this.localTransform; - if(this.dirty) + if(this._dirtyLocal !== this._versionLocal || + parentTransform._dirtyParentId !== parentTransform._transformId || + parentTransform._dirtyParentVersion !== parentTransform._versionGlobal ) { - // get the matrix values of the displayobject based on its transform properties.. - lt.a = this._cr * this.scale._x; - lt.b = this._sr * this.scale._x; - lt.c = -this._sr * this.scale._y; - lt.d = this._cr * this.scale._y; - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - } - - this.updated = this.dirty || parentTransform.updated; - - if(this.updated) - { + if(this._dirtyLocal !== this._versionLocal) + { + // get the matrix values of the displayobject based on its transform properties.. + lt.a = this._cr * this.scale._x; + lt.b = this._sr * this.scale._x; + lt.c = -this._sr * this.scale._y; + lt.d = this._cr * this.scale._y; + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._dirtyLocal = this._versionLocal; + } // concat the parent matrix with the objects transform. wt.a = lt.a * pt.a + lt.b * pt.c; wt.b = lt.a * pt.b + lt.b * pt.d; @@ -58,10 +63,12 @@ wt.d = lt.c * pt.b + lt.d * pt.d; wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._dirtyParentId = parentTransform._transformId; + this._dirtyParentVersion = parentTransform._versionGlobal; + this._versionGlobal++; } - - this.dirty = false; -} +}; module.exports = TransformStatic; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js new file mode 100644 index 0000000..1edb988 --- /dev/null +++ b/src/core/math/GroupD8.js @@ -0,0 +1,162 @@ +// Your friendly neighbour https://en.wikipedia.org/wiki/Dihedral_group of order 16 + +var ux = [1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1]; +var uy = [0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1]; +var vx = [0, -1, -1, -1, 0, 1, 1, 1, 0, 1, 1, 1, 0, -1, -1, -1]; +var vy = [1, 1, 0, -1, -1, -1, 0, 1, -1, -1, 0, 1, 1, 1, 0, -1]; +var tempMatrices = []; +var Matrix = require('./Matrix'); + +var mul = []; + +function signum(x) { + if (x < 0) { + return -1; + } + if (x > 0) { + return 1; + } + return 0; +} + +function init() { + for (var i = 0; i < 16; i++) { + var row = []; + mul.push(row); + for (var j = 0; j < 16; j++) { + var _ux = signum(ux[i] * ux[j] + vx[i] * uy[j]); + var _uy = signum(uy[i] * ux[j] + vy[i] * uy[j]); + var _vx = signum(ux[i] * vx[j] + vx[i] * vy[j]); + var _vy = signum(uy[i] * vx[j] + vy[i] * vy[j]); + for (var k = 0; k < 16; k++) { + if (ux[k] === _ux && uy[k] === _uy && vx[k] === _vx && vy[k] === _vy) { + row.push(k); + break; + } + } + } + } + + for (i=0;i<16;i++) { + var mat = new Matrix(); + mat.set(ux[i], uy[i], vx[i], vy[i], 0, 0); + tempMatrices.push(mat); + } +} + +init(); + +/** + * Implements Dihedral Group D_8, see [group D4]{@link http://mathworld.wolfram.com/DihedralGroupD4.html}, D8 is the same but with diagonals + * Used for texture rotations + * Vector xX(i), xY(i) is U-axis of sprite with rotation i + * Vector yY(i), yY(i) is V-axis of sprite with rotation i + * Rotations: 0 grad (0), 90 grad (2), 180 grad (4), 270 grad (6) + * Mirrors: vertical (8), main diagonal (10), horizontal (12), reverse diagonal (14) + * This is the small part of gameofbombs.com portal system. It works. + * @author Ivan @ivanpopelyshev + * + * @namespace PIXI.GroupD8 + */ +var GroupD8 = { + E: 0, + SE: 1, + S: 2, + SW: 3, + W: 4, + NW: 5, + N: 6, + NE: 7, + MIRROR_VERTICAL: 8, + MIRROR_HORIZONTAL: 12, + uX: function (ind) { + return ux[ind]; + }, + uY: function (ind) { + return uy[ind]; + }, + vX: function (ind) { + return vx[ind]; + }, + vY: function (ind) { + return vy[ind]; + }, + inv: function (rotation) { + if (rotation & 8) { + return rotation & 15; + } + return (-rotation) & 7; + }, + add: function (rotationSecond, rotationFirst) { + return mul[rotationSecond][rotationFirst]; + }, + sub: function (rotationSecond, rotationFirst) { + return mul[rotationSecond][GroupD8.inv(rotationFirst)]; + }, + /** + * Adds 180 degrees to rotation. Commutative operation + * @param rotation + * @returns {number} + */ + rotate180: function (rotation) { + return rotation ^ 4; + }, + /** + * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * @param rotation + * @returns {boolean} + */ + isSwapWidthHeight: function(rotation) { + return (rotation & 3) === 2; + }, + byDirection: function (dx, dy) { + if (Math.abs(dx) * 2 <= Math.abs(dy)) { + if (dy >= 0) { + return GroupD8.S; + } + else { + return GroupD8.N; + } + } else if (Math.abs(dy) * 2 <= Math.abs(dx)) { + if (dx > 0) { + return GroupD8.E; + } + else { + return GroupD8.W; + } + } else { + if (dy > 0) { + if (dx > 0) { + return GroupD8.SE; + } + else { + return GroupD8.SW; + } + } + else if (dx > 0) { + return GroupD8.NE; + } + else { + return GroupD8.NW; + } + } + }, + /** + * Helps sprite to compensate texture packer rotation. + * @param matrix {PIXI.Matrix} sprite world matrix + * @param rotation {number} + * @param tx {number|*} sprite anchoring + * @param ty {number|*} sprite anchoring + */ + matrixAppendRotationInv: function (matrix, rotation, tx, ty) { + //Packer used "rotation", we use "inv(rotation)" + var mat = tempMatrices[GroupD8.inv(rotation)]; + tx = tx || 0; + ty = ty || 0; + mat.tx = tx; + mat.ty = ty; + matrix.append(mat); + } +}; + +module.exports = GroupD8; diff --git a/src/core/display/ObservablePoint.js b/src/core/display/ObservablePoint.js index fd7a04b..958661f 100644 --- a/src/core/display/ObservablePoint.js +++ b/src/core/display/ObservablePoint.js @@ -35,7 +35,7 @@ set: function (value) { this._x = value; - this.transform.dirty = true; + this.transform._versionLocal++; } }, @@ -47,7 +47,7 @@ set: function (value) { this._y = value; - this.transform.dirty = true; + this.transform._versionLocal++; } } }); @@ -63,6 +63,6 @@ { this._x = x || 0; this._y = y || ( (y !== 0) ? this._x : 0 ); - - this.transform.dirty = true; + + this.transform._versionLocal++; }; diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index 61161d0..b86aa2d 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -1,6 +1,7 @@ var math = require('../math'); var ObservablePoint = require('./ObservablePoint'); +var generatorId = 0; /** * The Point object represents a location in a two-dimensional coordinate system, where x represents * the horizontal axis and y represents the vertical axis. @@ -19,13 +20,17 @@ this.scale = new ObservablePoint(this,1, 1); this.pivot = new ObservablePoint(this, 0, 0); this.skew = new ObservablePoint(this, 0,0); - + this.rotation = 0; this._sr = Math.sin(0); this._cr = Math.cos(0); - - this.dirty = true; - this.updated = true; + + this._versionLocal = 0; + this._versionGlobal = 0; + this._dirtyLocal = 0; + this._dirtyParentVersion = -1; + this._dirtyParentId = -1; + this._transformId = ++generatorId; } TransformStatic.prototype.constructor = TransformStatic; @@ -36,21 +41,21 @@ var wt = this.worldTransform; var lt = this.localTransform; - if(this.dirty) + if(this._dirtyLocal !== this._versionLocal || + parentTransform._dirtyParentId !== parentTransform._transformId || + parentTransform._dirtyParentVersion !== parentTransform._versionGlobal ) { - // get the matrix values of the displayobject based on its transform properties.. - lt.a = this._cr * this.scale._x; - lt.b = this._sr * this.scale._x; - lt.c = -this._sr * this.scale._y; - lt.d = this._cr * this.scale._y; - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - } - - this.updated = this.dirty || parentTransform.updated; - - if(this.updated) - { + if(this._dirtyLocal !== this._versionLocal) + { + // get the matrix values of the displayobject based on its transform properties.. + lt.a = this._cr * this.scale._x; + lt.b = this._sr * this.scale._x; + lt.c = -this._sr * this.scale._y; + lt.d = this._cr * this.scale._y; + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._dirtyLocal = this._versionLocal; + } // concat the parent matrix with the objects transform. wt.a = lt.a * pt.a + lt.b * pt.c; wt.b = lt.a * pt.b + lt.b * pt.d; @@ -58,10 +63,12 @@ wt.d = lt.c * pt.b + lt.d * pt.d; wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._dirtyParentId = parentTransform._transformId; + this._dirtyParentVersion = parentTransform._versionGlobal; + this._versionGlobal++; } - - this.dirty = false; -} +}; module.exports = TransformStatic; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js new file mode 100644 index 0000000..1edb988 --- /dev/null +++ b/src/core/math/GroupD8.js @@ -0,0 +1,162 @@ +// Your friendly neighbour https://en.wikipedia.org/wiki/Dihedral_group of order 16 + +var ux = [1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1]; +var uy = [0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1]; +var vx = [0, -1, -1, -1, 0, 1, 1, 1, 0, 1, 1, 1, 0, -1, -1, -1]; +var vy = [1, 1, 0, -1, -1, -1, 0, 1, -1, -1, 0, 1, 1, 1, 0, -1]; +var tempMatrices = []; +var Matrix = require('./Matrix'); + +var mul = []; + +function signum(x) { + if (x < 0) { + return -1; + } + if (x > 0) { + return 1; + } + return 0; +} + +function init() { + for (var i = 0; i < 16; i++) { + var row = []; + mul.push(row); + for (var j = 0; j < 16; j++) { + var _ux = signum(ux[i] * ux[j] + vx[i] * uy[j]); + var _uy = signum(uy[i] * ux[j] + vy[i] * uy[j]); + var _vx = signum(ux[i] * vx[j] + vx[i] * vy[j]); + var _vy = signum(uy[i] * vx[j] + vy[i] * vy[j]); + for (var k = 0; k < 16; k++) { + if (ux[k] === _ux && uy[k] === _uy && vx[k] === _vx && vy[k] === _vy) { + row.push(k); + break; + } + } + } + } + + for (i=0;i<16;i++) { + var mat = new Matrix(); + mat.set(ux[i], uy[i], vx[i], vy[i], 0, 0); + tempMatrices.push(mat); + } +} + +init(); + +/** + * Implements Dihedral Group D_8, see [group D4]{@link http://mathworld.wolfram.com/DihedralGroupD4.html}, D8 is the same but with diagonals + * Used for texture rotations + * Vector xX(i), xY(i) is U-axis of sprite with rotation i + * Vector yY(i), yY(i) is V-axis of sprite with rotation i + * Rotations: 0 grad (0), 90 grad (2), 180 grad (4), 270 grad (6) + * Mirrors: vertical (8), main diagonal (10), horizontal (12), reverse diagonal (14) + * This is the small part of gameofbombs.com portal system. It works. + * @author Ivan @ivanpopelyshev + * + * @namespace PIXI.GroupD8 + */ +var GroupD8 = { + E: 0, + SE: 1, + S: 2, + SW: 3, + W: 4, + NW: 5, + N: 6, + NE: 7, + MIRROR_VERTICAL: 8, + MIRROR_HORIZONTAL: 12, + uX: function (ind) { + return ux[ind]; + }, + uY: function (ind) { + return uy[ind]; + }, + vX: function (ind) { + return vx[ind]; + }, + vY: function (ind) { + return vy[ind]; + }, + inv: function (rotation) { + if (rotation & 8) { + return rotation & 15; + } + return (-rotation) & 7; + }, + add: function (rotationSecond, rotationFirst) { + return mul[rotationSecond][rotationFirst]; + }, + sub: function (rotationSecond, rotationFirst) { + return mul[rotationSecond][GroupD8.inv(rotationFirst)]; + }, + /** + * Adds 180 degrees to rotation. Commutative operation + * @param rotation + * @returns {number} + */ + rotate180: function (rotation) { + return rotation ^ 4; + }, + /** + * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * @param rotation + * @returns {boolean} + */ + isSwapWidthHeight: function(rotation) { + return (rotation & 3) === 2; + }, + byDirection: function (dx, dy) { + if (Math.abs(dx) * 2 <= Math.abs(dy)) { + if (dy >= 0) { + return GroupD8.S; + } + else { + return GroupD8.N; + } + } else if (Math.abs(dy) * 2 <= Math.abs(dx)) { + if (dx > 0) { + return GroupD8.E; + } + else { + return GroupD8.W; + } + } else { + if (dy > 0) { + if (dx > 0) { + return GroupD8.SE; + } + else { + return GroupD8.SW; + } + } + else if (dx > 0) { + return GroupD8.NE; + } + else { + return GroupD8.NW; + } + } + }, + /** + * Helps sprite to compensate texture packer rotation. + * @param matrix {PIXI.Matrix} sprite world matrix + * @param rotation {number} + * @param tx {number|*} sprite anchoring + * @param ty {number|*} sprite anchoring + */ + matrixAppendRotationInv: function (matrix, rotation, tx, ty) { + //Packer used "rotation", we use "inv(rotation)" + var mat = tempMatrices[GroupD8.inv(rotation)]; + tx = tx || 0; + ty = ty || 0; + mat.tx = tx; + mat.ty = ty; + matrix.append(mat); + } +}; + +module.exports = GroupD8; diff --git a/src/core/math/index.js b/src/core/math/index.js index fd0d9be..a43f6e7 100644 --- a/src/core/math/index.js +++ b/src/core/math/index.js @@ -11,6 +11,7 @@ Point: require('./Point'), Matrix: require('./Matrix'), + GroupD8: require('./GroupD8'), Circle: require('./shapes/Circle'), Ellipse: require('./shapes/Ellipse'), diff --git a/src/core/display/ObservablePoint.js b/src/core/display/ObservablePoint.js index fd7a04b..958661f 100644 --- a/src/core/display/ObservablePoint.js +++ b/src/core/display/ObservablePoint.js @@ -35,7 +35,7 @@ set: function (value) { this._x = value; - this.transform.dirty = true; + this.transform._versionLocal++; } }, @@ -47,7 +47,7 @@ set: function (value) { this._y = value; - this.transform.dirty = true; + this.transform._versionLocal++; } } }); @@ -63,6 +63,6 @@ { this._x = x || 0; this._y = y || ( (y !== 0) ? this._x : 0 ); - - this.transform.dirty = true; + + this.transform._versionLocal++; }; diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index 61161d0..b86aa2d 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -1,6 +1,7 @@ var math = require('../math'); var ObservablePoint = require('./ObservablePoint'); +var generatorId = 0; /** * The Point object represents a location in a two-dimensional coordinate system, where x represents * the horizontal axis and y represents the vertical axis. @@ -19,13 +20,17 @@ this.scale = new ObservablePoint(this,1, 1); this.pivot = new ObservablePoint(this, 0, 0); this.skew = new ObservablePoint(this, 0,0); - + this.rotation = 0; this._sr = Math.sin(0); this._cr = Math.cos(0); - - this.dirty = true; - this.updated = true; + + this._versionLocal = 0; + this._versionGlobal = 0; + this._dirtyLocal = 0; + this._dirtyParentVersion = -1; + this._dirtyParentId = -1; + this._transformId = ++generatorId; } TransformStatic.prototype.constructor = TransformStatic; @@ -36,21 +41,21 @@ var wt = this.worldTransform; var lt = this.localTransform; - if(this.dirty) + if(this._dirtyLocal !== this._versionLocal || + parentTransform._dirtyParentId !== parentTransform._transformId || + parentTransform._dirtyParentVersion !== parentTransform._versionGlobal ) { - // get the matrix values of the displayobject based on its transform properties.. - lt.a = this._cr * this.scale._x; - lt.b = this._sr * this.scale._x; - lt.c = -this._sr * this.scale._y; - lt.d = this._cr * this.scale._y; - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - } - - this.updated = this.dirty || parentTransform.updated; - - if(this.updated) - { + if(this._dirtyLocal !== this._versionLocal) + { + // get the matrix values of the displayobject based on its transform properties.. + lt.a = this._cr * this.scale._x; + lt.b = this._sr * this.scale._x; + lt.c = -this._sr * this.scale._y; + lt.d = this._cr * this.scale._y; + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._dirtyLocal = this._versionLocal; + } // concat the parent matrix with the objects transform. wt.a = lt.a * pt.a + lt.b * pt.c; wt.b = lt.a * pt.b + lt.b * pt.d; @@ -58,10 +63,12 @@ wt.d = lt.c * pt.b + lt.d * pt.d; wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._dirtyParentId = parentTransform._transformId; + this._dirtyParentVersion = parentTransform._versionGlobal; + this._versionGlobal++; } - - this.dirty = false; -} +}; module.exports = TransformStatic; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js new file mode 100644 index 0000000..1edb988 --- /dev/null +++ b/src/core/math/GroupD8.js @@ -0,0 +1,162 @@ +// Your friendly neighbour https://en.wikipedia.org/wiki/Dihedral_group of order 16 + +var ux = [1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1]; +var uy = [0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1]; +var vx = [0, -1, -1, -1, 0, 1, 1, 1, 0, 1, 1, 1, 0, -1, -1, -1]; +var vy = [1, 1, 0, -1, -1, -1, 0, 1, -1, -1, 0, 1, 1, 1, 0, -1]; +var tempMatrices = []; +var Matrix = require('./Matrix'); + +var mul = []; + +function signum(x) { + if (x < 0) { + return -1; + } + if (x > 0) { + return 1; + } + return 0; +} + +function init() { + for (var i = 0; i < 16; i++) { + var row = []; + mul.push(row); + for (var j = 0; j < 16; j++) { + var _ux = signum(ux[i] * ux[j] + vx[i] * uy[j]); + var _uy = signum(uy[i] * ux[j] + vy[i] * uy[j]); + var _vx = signum(ux[i] * vx[j] + vx[i] * vy[j]); + var _vy = signum(uy[i] * vx[j] + vy[i] * vy[j]); + for (var k = 0; k < 16; k++) { + if (ux[k] === _ux && uy[k] === _uy && vx[k] === _vx && vy[k] === _vy) { + row.push(k); + break; + } + } + } + } + + for (i=0;i<16;i++) { + var mat = new Matrix(); + mat.set(ux[i], uy[i], vx[i], vy[i], 0, 0); + tempMatrices.push(mat); + } +} + +init(); + +/** + * Implements Dihedral Group D_8, see [group D4]{@link http://mathworld.wolfram.com/DihedralGroupD4.html}, D8 is the same but with diagonals + * Used for texture rotations + * Vector xX(i), xY(i) is U-axis of sprite with rotation i + * Vector yY(i), yY(i) is V-axis of sprite with rotation i + * Rotations: 0 grad (0), 90 grad (2), 180 grad (4), 270 grad (6) + * Mirrors: vertical (8), main diagonal (10), horizontal (12), reverse diagonal (14) + * This is the small part of gameofbombs.com portal system. It works. + * @author Ivan @ivanpopelyshev + * + * @namespace PIXI.GroupD8 + */ +var GroupD8 = { + E: 0, + SE: 1, + S: 2, + SW: 3, + W: 4, + NW: 5, + N: 6, + NE: 7, + MIRROR_VERTICAL: 8, + MIRROR_HORIZONTAL: 12, + uX: function (ind) { + return ux[ind]; + }, + uY: function (ind) { + return uy[ind]; + }, + vX: function (ind) { + return vx[ind]; + }, + vY: function (ind) { + return vy[ind]; + }, + inv: function (rotation) { + if (rotation & 8) { + return rotation & 15; + } + return (-rotation) & 7; + }, + add: function (rotationSecond, rotationFirst) { + return mul[rotationSecond][rotationFirst]; + }, + sub: function (rotationSecond, rotationFirst) { + return mul[rotationSecond][GroupD8.inv(rotationFirst)]; + }, + /** + * Adds 180 degrees to rotation. Commutative operation + * @param rotation + * @returns {number} + */ + rotate180: function (rotation) { + return rotation ^ 4; + }, + /** + * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * @param rotation + * @returns {boolean} + */ + isSwapWidthHeight: function(rotation) { + return (rotation & 3) === 2; + }, + byDirection: function (dx, dy) { + if (Math.abs(dx) * 2 <= Math.abs(dy)) { + if (dy >= 0) { + return GroupD8.S; + } + else { + return GroupD8.N; + } + } else if (Math.abs(dy) * 2 <= Math.abs(dx)) { + if (dx > 0) { + return GroupD8.E; + } + else { + return GroupD8.W; + } + } else { + if (dy > 0) { + if (dx > 0) { + return GroupD8.SE; + } + else { + return GroupD8.SW; + } + } + else if (dx > 0) { + return GroupD8.NE; + } + else { + return GroupD8.NW; + } + } + }, + /** + * Helps sprite to compensate texture packer rotation. + * @param matrix {PIXI.Matrix} sprite world matrix + * @param rotation {number} + * @param tx {number|*} sprite anchoring + * @param ty {number|*} sprite anchoring + */ + matrixAppendRotationInv: function (matrix, rotation, tx, ty) { + //Packer used "rotation", we use "inv(rotation)" + var mat = tempMatrices[GroupD8.inv(rotation)]; + tx = tx || 0; + ty = ty || 0; + mat.tx = tx; + mat.ty = ty; + matrix.append(mat); + } +}; + +module.exports = GroupD8; diff --git a/src/core/math/index.js b/src/core/math/index.js index fd0d9be..a43f6e7 100644 --- a/src/core/math/index.js +++ b/src/core/math/index.js @@ -11,6 +11,7 @@ Point: require('./Point'), Matrix: require('./Matrix'), + GroupD8: require('./GroupD8'), Circle: require('./shapes/Circle'), Ellipse: require('./shapes/Ellipse'), diff --git a/src/core/particles/webgl/ParticleRenderer.js b/src/core/particles/webgl/ParticleRenderer.js index 8481c92..0b0abd0 100644 --- a/src/core/particles/webgl/ParticleRenderer.js +++ b/src/core/particles/webgl/ParticleRenderer.js @@ -285,25 +285,25 @@ texture = sprite._texture; sx = sprite.scale.x; sy = sprite.scale.y; + var trim = texture.trim, crop = texture.crop; - if (texture.trim) + if (trim) { - // if the sprite is trimmed then we need to add the extra space before transforming the sprite coords.. - trim = texture.trim; + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - sprite.anchor.x * crop.width; + w0 = w1 + trim.width; - w1 = trim.x - sprite.anchor.x * trim.width; - w0 = w1 + texture.crop.width; + h1 = trim.y - sprite.anchor.y * crop.height; + h0 = h1 + trim.height; - h1 = trim.y - sprite.anchor.y * trim.height; - h0 = h1 + texture.crop.height; } else { - w0 = (texture._frame.width ) * (1-sprite.anchor.x); - w1 = (texture._frame.width ) * -sprite.anchor.x; + w0 = (crop.width ) * (1-sprite.anchor.x); + w1 = (crop.width ) * -sprite.anchor.x; - h0 = texture._frame.height * (1-sprite.anchor.y); - h1 = texture._frame.height * -sprite.anchor.y; + h0 = crop.height * (1-sprite.anchor.y); + h1 = crop.height * -sprite.anchor.y; } array[offset] = w1 * sx; diff --git a/src/core/display/ObservablePoint.js b/src/core/display/ObservablePoint.js index fd7a04b..958661f 100644 --- a/src/core/display/ObservablePoint.js +++ b/src/core/display/ObservablePoint.js @@ -35,7 +35,7 @@ set: function (value) { this._x = value; - this.transform.dirty = true; + this.transform._versionLocal++; } }, @@ -47,7 +47,7 @@ set: function (value) { this._y = value; - this.transform.dirty = true; + this.transform._versionLocal++; } } }); @@ -63,6 +63,6 @@ { this._x = x || 0; this._y = y || ( (y !== 0) ? this._x : 0 ); - - this.transform.dirty = true; + + this.transform._versionLocal++; }; diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index 61161d0..b86aa2d 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -1,6 +1,7 @@ var math = require('../math'); var ObservablePoint = require('./ObservablePoint'); +var generatorId = 0; /** * The Point object represents a location in a two-dimensional coordinate system, where x represents * the horizontal axis and y represents the vertical axis. @@ -19,13 +20,17 @@ this.scale = new ObservablePoint(this,1, 1); this.pivot = new ObservablePoint(this, 0, 0); this.skew = new ObservablePoint(this, 0,0); - + this.rotation = 0; this._sr = Math.sin(0); this._cr = Math.cos(0); - - this.dirty = true; - this.updated = true; + + this._versionLocal = 0; + this._versionGlobal = 0; + this._dirtyLocal = 0; + this._dirtyParentVersion = -1; + this._dirtyParentId = -1; + this._transformId = ++generatorId; } TransformStatic.prototype.constructor = TransformStatic; @@ -36,21 +41,21 @@ var wt = this.worldTransform; var lt = this.localTransform; - if(this.dirty) + if(this._dirtyLocal !== this._versionLocal || + parentTransform._dirtyParentId !== parentTransform._transformId || + parentTransform._dirtyParentVersion !== parentTransform._versionGlobal ) { - // get the matrix values of the displayobject based on its transform properties.. - lt.a = this._cr * this.scale._x; - lt.b = this._sr * this.scale._x; - lt.c = -this._sr * this.scale._y; - lt.d = this._cr * this.scale._y; - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - } - - this.updated = this.dirty || parentTransform.updated; - - if(this.updated) - { + if(this._dirtyLocal !== this._versionLocal) + { + // get the matrix values of the displayobject based on its transform properties.. + lt.a = this._cr * this.scale._x; + lt.b = this._sr * this.scale._x; + lt.c = -this._sr * this.scale._y; + lt.d = this._cr * this.scale._y; + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._dirtyLocal = this._versionLocal; + } // concat the parent matrix with the objects transform. wt.a = lt.a * pt.a + lt.b * pt.c; wt.b = lt.a * pt.b + lt.b * pt.d; @@ -58,10 +63,12 @@ wt.d = lt.c * pt.b + lt.d * pt.d; wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._dirtyParentId = parentTransform._transformId; + this._dirtyParentVersion = parentTransform._versionGlobal; + this._versionGlobal++; } - - this.dirty = false; -} +}; module.exports = TransformStatic; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js new file mode 100644 index 0000000..1edb988 --- /dev/null +++ b/src/core/math/GroupD8.js @@ -0,0 +1,162 @@ +// Your friendly neighbour https://en.wikipedia.org/wiki/Dihedral_group of order 16 + +var ux = [1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1]; +var uy = [0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1]; +var vx = [0, -1, -1, -1, 0, 1, 1, 1, 0, 1, 1, 1, 0, -1, -1, -1]; +var vy = [1, 1, 0, -1, -1, -1, 0, 1, -1, -1, 0, 1, 1, 1, 0, -1]; +var tempMatrices = []; +var Matrix = require('./Matrix'); + +var mul = []; + +function signum(x) { + if (x < 0) { + return -1; + } + if (x > 0) { + return 1; + } + return 0; +} + +function init() { + for (var i = 0; i < 16; i++) { + var row = []; + mul.push(row); + for (var j = 0; j < 16; j++) { + var _ux = signum(ux[i] * ux[j] + vx[i] * uy[j]); + var _uy = signum(uy[i] * ux[j] + vy[i] * uy[j]); + var _vx = signum(ux[i] * vx[j] + vx[i] * vy[j]); + var _vy = signum(uy[i] * vx[j] + vy[i] * vy[j]); + for (var k = 0; k < 16; k++) { + if (ux[k] === _ux && uy[k] === _uy && vx[k] === _vx && vy[k] === _vy) { + row.push(k); + break; + } + } + } + } + + for (i=0;i<16;i++) { + var mat = new Matrix(); + mat.set(ux[i], uy[i], vx[i], vy[i], 0, 0); + tempMatrices.push(mat); + } +} + +init(); + +/** + * Implements Dihedral Group D_8, see [group D4]{@link http://mathworld.wolfram.com/DihedralGroupD4.html}, D8 is the same but with diagonals + * Used for texture rotations + * Vector xX(i), xY(i) is U-axis of sprite with rotation i + * Vector yY(i), yY(i) is V-axis of sprite with rotation i + * Rotations: 0 grad (0), 90 grad (2), 180 grad (4), 270 grad (6) + * Mirrors: vertical (8), main diagonal (10), horizontal (12), reverse diagonal (14) + * This is the small part of gameofbombs.com portal system. It works. + * @author Ivan @ivanpopelyshev + * + * @namespace PIXI.GroupD8 + */ +var GroupD8 = { + E: 0, + SE: 1, + S: 2, + SW: 3, + W: 4, + NW: 5, + N: 6, + NE: 7, + MIRROR_VERTICAL: 8, + MIRROR_HORIZONTAL: 12, + uX: function (ind) { + return ux[ind]; + }, + uY: function (ind) { + return uy[ind]; + }, + vX: function (ind) { + return vx[ind]; + }, + vY: function (ind) { + return vy[ind]; + }, + inv: function (rotation) { + if (rotation & 8) { + return rotation & 15; + } + return (-rotation) & 7; + }, + add: function (rotationSecond, rotationFirst) { + return mul[rotationSecond][rotationFirst]; + }, + sub: function (rotationSecond, rotationFirst) { + return mul[rotationSecond][GroupD8.inv(rotationFirst)]; + }, + /** + * Adds 180 degrees to rotation. Commutative operation + * @param rotation + * @returns {number} + */ + rotate180: function (rotation) { + return rotation ^ 4; + }, + /** + * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * @param rotation + * @returns {boolean} + */ + isSwapWidthHeight: function(rotation) { + return (rotation & 3) === 2; + }, + byDirection: function (dx, dy) { + if (Math.abs(dx) * 2 <= Math.abs(dy)) { + if (dy >= 0) { + return GroupD8.S; + } + else { + return GroupD8.N; + } + } else if (Math.abs(dy) * 2 <= Math.abs(dx)) { + if (dx > 0) { + return GroupD8.E; + } + else { + return GroupD8.W; + } + } else { + if (dy > 0) { + if (dx > 0) { + return GroupD8.SE; + } + else { + return GroupD8.SW; + } + } + else if (dx > 0) { + return GroupD8.NE; + } + else { + return GroupD8.NW; + } + } + }, + /** + * Helps sprite to compensate texture packer rotation. + * @param matrix {PIXI.Matrix} sprite world matrix + * @param rotation {number} + * @param tx {number|*} sprite anchoring + * @param ty {number|*} sprite anchoring + */ + matrixAppendRotationInv: function (matrix, rotation, tx, ty) { + //Packer used "rotation", we use "inv(rotation)" + var mat = tempMatrices[GroupD8.inv(rotation)]; + tx = tx || 0; + ty = ty || 0; + mat.tx = tx; + mat.ty = ty; + matrix.append(mat); + } +}; + +module.exports = GroupD8; diff --git a/src/core/math/index.js b/src/core/math/index.js index fd0d9be..a43f6e7 100644 --- a/src/core/math/index.js +++ b/src/core/math/index.js @@ -11,6 +11,7 @@ Point: require('./Point'), Matrix: require('./Matrix'), + GroupD8: require('./GroupD8'), Circle: require('./shapes/Circle'), Ellipse: require('./shapes/Ellipse'), diff --git a/src/core/particles/webgl/ParticleRenderer.js b/src/core/particles/webgl/ParticleRenderer.js index 8481c92..0b0abd0 100644 --- a/src/core/particles/webgl/ParticleRenderer.js +++ b/src/core/particles/webgl/ParticleRenderer.js @@ -285,25 +285,25 @@ texture = sprite._texture; sx = sprite.scale.x; sy = sprite.scale.y; + var trim = texture.trim, crop = texture.crop; - if (texture.trim) + if (trim) { - // if the sprite is trimmed then we need to add the extra space before transforming the sprite coords.. - trim = texture.trim; + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - sprite.anchor.x * crop.width; + w0 = w1 + trim.width; - w1 = trim.x - sprite.anchor.x * trim.width; - w0 = w1 + texture.crop.width; + h1 = trim.y - sprite.anchor.y * crop.height; + h0 = h1 + trim.height; - h1 = trim.y - sprite.anchor.y * trim.height; - h0 = h1 + texture.crop.height; } else { - w0 = (texture._frame.width ) * (1-sprite.anchor.x); - w1 = (texture._frame.width ) * -sprite.anchor.x; + w0 = (crop.width ) * (1-sprite.anchor.x); + w1 = (crop.width ) * -sprite.anchor.x; - h0 = texture._frame.height * (1-sprite.anchor.y); - h1 = texture._frame.height * -sprite.anchor.y; + h0 = crop.height * (1-sprite.anchor.y); + h1 = crop.height * -sprite.anchor.y; } array[offset] = w1 * sx; diff --git a/src/core/renderers/canvas/utils/CanvasTinter.js b/src/core/renderers/canvas/utils/CanvasTinter.js index eedf175..8827558 100644 --- a/src/core/renderers/canvas/utils/CanvasTinter.js +++ b/src/core/renderers/canvas/utils/CanvasTinter.js @@ -68,11 +68,7 @@ var resolution = texture.baseTexture.resolution; - var crop = texture.crop.clone(); - crop.x *= resolution; - crop.y *= resolution; - crop.width *= resolution; - crop.height *= resolution; + var crop = texture._frame; canvas.width = crop.width; canvas.height = crop.height; @@ -123,11 +119,7 @@ var resolution = texture.baseTexture.resolution; - var crop = texture.crop.clone(); - crop.x *= resolution; - crop.y *= resolution; - crop.width *= resolution; - crop.height *= resolution; + var crop = texture._frame; canvas.width = crop.width; canvas.height = crop.height; @@ -165,11 +157,7 @@ var resolution = texture.baseTexture.resolution; - var crop = texture.crop.clone(); - crop.x *= resolution; - crop.y *= resolution; - crop.width *= resolution; - crop.height *= resolution; + var crop = texture._frame; canvas.width = crop.width; canvas.height = crop.height; diff --git a/src/core/display/ObservablePoint.js b/src/core/display/ObservablePoint.js index fd7a04b..958661f 100644 --- a/src/core/display/ObservablePoint.js +++ b/src/core/display/ObservablePoint.js @@ -35,7 +35,7 @@ set: function (value) { this._x = value; - this.transform.dirty = true; + this.transform._versionLocal++; } }, @@ -47,7 +47,7 @@ set: function (value) { this._y = value; - this.transform.dirty = true; + this.transform._versionLocal++; } } }); @@ -63,6 +63,6 @@ { this._x = x || 0; this._y = y || ( (y !== 0) ? this._x : 0 ); - - this.transform.dirty = true; + + this.transform._versionLocal++; }; diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index 61161d0..b86aa2d 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -1,6 +1,7 @@ var math = require('../math'); var ObservablePoint = require('./ObservablePoint'); +var generatorId = 0; /** * The Point object represents a location in a two-dimensional coordinate system, where x represents * the horizontal axis and y represents the vertical axis. @@ -19,13 +20,17 @@ this.scale = new ObservablePoint(this,1, 1); this.pivot = new ObservablePoint(this, 0, 0); this.skew = new ObservablePoint(this, 0,0); - + this.rotation = 0; this._sr = Math.sin(0); this._cr = Math.cos(0); - - this.dirty = true; - this.updated = true; + + this._versionLocal = 0; + this._versionGlobal = 0; + this._dirtyLocal = 0; + this._dirtyParentVersion = -1; + this._dirtyParentId = -1; + this._transformId = ++generatorId; } TransformStatic.prototype.constructor = TransformStatic; @@ -36,21 +41,21 @@ var wt = this.worldTransform; var lt = this.localTransform; - if(this.dirty) + if(this._dirtyLocal !== this._versionLocal || + parentTransform._dirtyParentId !== parentTransform._transformId || + parentTransform._dirtyParentVersion !== parentTransform._versionGlobal ) { - // get the matrix values of the displayobject based on its transform properties.. - lt.a = this._cr * this.scale._x; - lt.b = this._sr * this.scale._x; - lt.c = -this._sr * this.scale._y; - lt.d = this._cr * this.scale._y; - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - } - - this.updated = this.dirty || parentTransform.updated; - - if(this.updated) - { + if(this._dirtyLocal !== this._versionLocal) + { + // get the matrix values of the displayobject based on its transform properties.. + lt.a = this._cr * this.scale._x; + lt.b = this._sr * this.scale._x; + lt.c = -this._sr * this.scale._y; + lt.d = this._cr * this.scale._y; + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._dirtyLocal = this._versionLocal; + } // concat the parent matrix with the objects transform. wt.a = lt.a * pt.a + lt.b * pt.c; wt.b = lt.a * pt.b + lt.b * pt.d; @@ -58,10 +63,12 @@ wt.d = lt.c * pt.b + lt.d * pt.d; wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._dirtyParentId = parentTransform._transformId; + this._dirtyParentVersion = parentTransform._versionGlobal; + this._versionGlobal++; } - - this.dirty = false; -} +}; module.exports = TransformStatic; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js new file mode 100644 index 0000000..1edb988 --- /dev/null +++ b/src/core/math/GroupD8.js @@ -0,0 +1,162 @@ +// Your friendly neighbour https://en.wikipedia.org/wiki/Dihedral_group of order 16 + +var ux = [1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1]; +var uy = [0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1]; +var vx = [0, -1, -1, -1, 0, 1, 1, 1, 0, 1, 1, 1, 0, -1, -1, -1]; +var vy = [1, 1, 0, -1, -1, -1, 0, 1, -1, -1, 0, 1, 1, 1, 0, -1]; +var tempMatrices = []; +var Matrix = require('./Matrix'); + +var mul = []; + +function signum(x) { + if (x < 0) { + return -1; + } + if (x > 0) { + return 1; + } + return 0; +} + +function init() { + for (var i = 0; i < 16; i++) { + var row = []; + mul.push(row); + for (var j = 0; j < 16; j++) { + var _ux = signum(ux[i] * ux[j] + vx[i] * uy[j]); + var _uy = signum(uy[i] * ux[j] + vy[i] * uy[j]); + var _vx = signum(ux[i] * vx[j] + vx[i] * vy[j]); + var _vy = signum(uy[i] * vx[j] + vy[i] * vy[j]); + for (var k = 0; k < 16; k++) { + if (ux[k] === _ux && uy[k] === _uy && vx[k] === _vx && vy[k] === _vy) { + row.push(k); + break; + } + } + } + } + + for (i=0;i<16;i++) { + var mat = new Matrix(); + mat.set(ux[i], uy[i], vx[i], vy[i], 0, 0); + tempMatrices.push(mat); + } +} + +init(); + +/** + * Implements Dihedral Group D_8, see [group D4]{@link http://mathworld.wolfram.com/DihedralGroupD4.html}, D8 is the same but with diagonals + * Used for texture rotations + * Vector xX(i), xY(i) is U-axis of sprite with rotation i + * Vector yY(i), yY(i) is V-axis of sprite with rotation i + * Rotations: 0 grad (0), 90 grad (2), 180 grad (4), 270 grad (6) + * Mirrors: vertical (8), main diagonal (10), horizontal (12), reverse diagonal (14) + * This is the small part of gameofbombs.com portal system. It works. + * @author Ivan @ivanpopelyshev + * + * @namespace PIXI.GroupD8 + */ +var GroupD8 = { + E: 0, + SE: 1, + S: 2, + SW: 3, + W: 4, + NW: 5, + N: 6, + NE: 7, + MIRROR_VERTICAL: 8, + MIRROR_HORIZONTAL: 12, + uX: function (ind) { + return ux[ind]; + }, + uY: function (ind) { + return uy[ind]; + }, + vX: function (ind) { + return vx[ind]; + }, + vY: function (ind) { + return vy[ind]; + }, + inv: function (rotation) { + if (rotation & 8) { + return rotation & 15; + } + return (-rotation) & 7; + }, + add: function (rotationSecond, rotationFirst) { + return mul[rotationSecond][rotationFirst]; + }, + sub: function (rotationSecond, rotationFirst) { + return mul[rotationSecond][GroupD8.inv(rotationFirst)]; + }, + /** + * Adds 180 degrees to rotation. Commutative operation + * @param rotation + * @returns {number} + */ + rotate180: function (rotation) { + return rotation ^ 4; + }, + /** + * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * @param rotation + * @returns {boolean} + */ + isSwapWidthHeight: function(rotation) { + return (rotation & 3) === 2; + }, + byDirection: function (dx, dy) { + if (Math.abs(dx) * 2 <= Math.abs(dy)) { + if (dy >= 0) { + return GroupD8.S; + } + else { + return GroupD8.N; + } + } else if (Math.abs(dy) * 2 <= Math.abs(dx)) { + if (dx > 0) { + return GroupD8.E; + } + else { + return GroupD8.W; + } + } else { + if (dy > 0) { + if (dx > 0) { + return GroupD8.SE; + } + else { + return GroupD8.SW; + } + } + else if (dx > 0) { + return GroupD8.NE; + } + else { + return GroupD8.NW; + } + } + }, + /** + * Helps sprite to compensate texture packer rotation. + * @param matrix {PIXI.Matrix} sprite world matrix + * @param rotation {number} + * @param tx {number|*} sprite anchoring + * @param ty {number|*} sprite anchoring + */ + matrixAppendRotationInv: function (matrix, rotation, tx, ty) { + //Packer used "rotation", we use "inv(rotation)" + var mat = tempMatrices[GroupD8.inv(rotation)]; + tx = tx || 0; + ty = ty || 0; + mat.tx = tx; + mat.ty = ty; + matrix.append(mat); + } +}; + +module.exports = GroupD8; diff --git a/src/core/math/index.js b/src/core/math/index.js index fd0d9be..a43f6e7 100644 --- a/src/core/math/index.js +++ b/src/core/math/index.js @@ -11,6 +11,7 @@ Point: require('./Point'), Matrix: require('./Matrix'), + GroupD8: require('./GroupD8'), Circle: require('./shapes/Circle'), Ellipse: require('./shapes/Ellipse'), diff --git a/src/core/particles/webgl/ParticleRenderer.js b/src/core/particles/webgl/ParticleRenderer.js index 8481c92..0b0abd0 100644 --- a/src/core/particles/webgl/ParticleRenderer.js +++ b/src/core/particles/webgl/ParticleRenderer.js @@ -285,25 +285,25 @@ texture = sprite._texture; sx = sprite.scale.x; sy = sprite.scale.y; + var trim = texture.trim, crop = texture.crop; - if (texture.trim) + if (trim) { - // if the sprite is trimmed then we need to add the extra space before transforming the sprite coords.. - trim = texture.trim; + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - sprite.anchor.x * crop.width; + w0 = w1 + trim.width; - w1 = trim.x - sprite.anchor.x * trim.width; - w0 = w1 + texture.crop.width; + h1 = trim.y - sprite.anchor.y * crop.height; + h0 = h1 + trim.height; - h1 = trim.y - sprite.anchor.y * trim.height; - h0 = h1 + texture.crop.height; } else { - w0 = (texture._frame.width ) * (1-sprite.anchor.x); - w1 = (texture._frame.width ) * -sprite.anchor.x; + w0 = (crop.width ) * (1-sprite.anchor.x); + w1 = (crop.width ) * -sprite.anchor.x; - h0 = texture._frame.height * (1-sprite.anchor.y); - h1 = texture._frame.height * -sprite.anchor.y; + h0 = crop.height * (1-sprite.anchor.y); + h1 = crop.height * -sprite.anchor.y; } array[offset] = w1 * sx; diff --git a/src/core/renderers/canvas/utils/CanvasTinter.js b/src/core/renderers/canvas/utils/CanvasTinter.js index eedf175..8827558 100644 --- a/src/core/renderers/canvas/utils/CanvasTinter.js +++ b/src/core/renderers/canvas/utils/CanvasTinter.js @@ -68,11 +68,7 @@ var resolution = texture.baseTexture.resolution; - var crop = texture.crop.clone(); - crop.x *= resolution; - crop.y *= resolution; - crop.width *= resolution; - crop.height *= resolution; + var crop = texture._frame; canvas.width = crop.width; canvas.height = crop.height; @@ -123,11 +119,7 @@ var resolution = texture.baseTexture.resolution; - var crop = texture.crop.clone(); - crop.x *= resolution; - crop.y *= resolution; - crop.width *= resolution; - crop.height *= resolution; + var crop = texture._frame; canvas.width = crop.width; canvas.height = crop.height; @@ -165,11 +157,7 @@ var resolution = texture.baseTexture.resolution; - var crop = texture.crop.clone(); - crop.x *= resolution; - crop.y *= resolution; - crop.width *= resolution; - crop.height *= resolution; + var crop = texture._frame; canvas.width = crop.width; canvas.height = crop.height; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fcf53cb..1b79373 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -111,12 +111,12 @@ width: { get: function () { - return Math.abs(this.scale.x) * this.texture._frame.width; + return Math.abs(this.scale.x) * this.texture.crop.width; }, set: function (value) { var sign = utils.sign(this.scale.x) || 1; - this.scale.x = sign * value / this.texture._frame.width; + this.scale.x = sign * value / this.texture.crop.width; this._width = value; } }, @@ -130,12 +130,12 @@ height: { get: function () { - return Math.abs(this.scale.y) * this.texture._frame.height; + return Math.abs(this.scale.y) * this.texture.crop.height; }, set: function (value) { var sign = utils.sign(this.scale.y) || 1; - this.scale.y = sign * value / this.texture._frame.height; + this.scale.y = sign * value / this.texture.crop.height; this._height = value; } }, @@ -191,12 +191,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.frame.width; + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.crop.width; } if (this._height) { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.frame.height; + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.crop.height; } }; @@ -207,25 +207,26 @@ a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, vertexData = this.vertexData, w0, w1, h0, h1, - trim = texture.trim; - + trim = texture.trim, + crop = texture.crop; + if (trim) { // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor.x * trim.width; - w0 = w1 + texture.crop.width; + w1 = trim.x - this.anchor.x * crop.width; + w0 = w1 + trim.width; - h1 = trim.y - this.anchor.y * trim.height; - h0 = h1 + texture.crop.height; + h1 = trim.y - this.anchor.y * crop.height; + h0 = h1 + trim.height; } else { - w0 = (texture._frame.width ) * (1-this.anchor.x); - w1 = (texture._frame.width ) * -this.anchor.x; + w0 = (crop.width ) * (1-this.anchor.x); + w1 = (crop.width ) * -this.anchor.x; - h0 = texture._frame.height * (1-this.anchor.y); - h1 = texture._frame.height * -this.anchor.y; + h0 = crop.height * (1-this.anchor.y); + h1 = crop.height * -this.anchor.y; } // xy @@ -244,7 +245,6 @@ vertexData[6] = a * w1 + c * h0 + tx; vertexData[7] = d * h0 + b * w1 + ty; - } /** @@ -262,7 +262,7 @@ // set the vertex data this.caclulateVertices(); } - + renderer.setObjectRenderer(renderer.plugins.sprite); renderer.plugins.sprite.render(this); }; @@ -362,10 +362,10 @@ */ Sprite.prototype.getLocalBounds = function () { - this._bounds.x = -this._texture._frame.width * this.anchor.x; - this._bounds.y = -this._texture._frame.height * this.anchor.y; - this._bounds.width = this._texture._frame.width; - this._bounds.height = this._texture._frame.height; + this._bounds.x = -this._texture.crop.width * this.anchor.x; + this._bounds.y = -this._texture.crop.height * this.anchor.y; + this._bounds.width = this._texture.crop.width; + this._bounds.height = this._texture.crop.height; return this._bounds; }; @@ -379,8 +379,8 @@ { this.worldTransform.applyInverse(point, tempPoint); - var width = this._texture._frame.width; - var height = this._texture._frame.height; + var width = this._texture.crop.width; + var height = this._texture.crop.height; var x1 = -width * this.anchor.x; var y1; @@ -423,8 +423,8 @@ wt = this.worldTransform, dx, dy, - width, - height; + width = texture._frame.width, + height = texture._frame.height; renderer.context.globalAlpha = this.worldAlpha; @@ -435,45 +435,23 @@ renderer.context[renderer.smoothProperty] = smoothingEnabled; } - // If the texture is trimmed we offset by the trim x/y, otherwise we use the frame dimensions - - if(texture.rotate) - { - width = texture.crop.height; - height = texture.crop.width; - - dx = (texture.trim) ? texture.trim.y - this.anchor.y * texture.trim.height : this.anchor.y * -texture._frame.height; - dy = (texture.trim) ? texture.trim.x - this.anchor.x * texture.trim.width : this.anchor.x * -texture._frame.width; - - dx += width; - - wt.tx = dy * wt.a + dx * wt.c + wt.tx; - wt.ty = dy * wt.b + dx * wt.d + wt.ty; - - var temp = wt.a; - wt.a = -wt.c; - wt.c = temp; - - temp = wt.b; - wt.b = -wt.d; - wt.d = temp; - + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - this.anchor.x * texture.crop.width; + dy = texture.trim.height/2 + texture.trim.y - this.anchor.y * texture.crop.height; + } else { + dx = (0.5 - this.anchor.x) * texture.crop.width; + dy = (0.5 - this.anchor.y) * texture.crop.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); // the anchor has already been applied above, so lets set it to zero dx = 0; dy = 0; - } - else - { - width = texture.crop.width; - height = texture.crop.height; - - dx = (texture.trim) ? texture.trim.x - this.anchor.x * texture.trim.width : this.anchor.x * -texture._frame.width; - dy = (texture.trim) ? texture.trim.y - this.anchor.y * texture.trim.height : this.anchor.y * -texture._frame.height; - } - - - + dx -= width/2; + dy -= height/2; // Allow for pixel rounding if (renderer.roundPixels) { @@ -491,7 +469,6 @@ } else { - renderer.context.setTransform( wt.a, wt.b, @@ -500,8 +477,6 @@ wt.tx * renderer.resolution, wt.ty * renderer.resolution ); - - } var resolution = texture.baseTexture.resolution; @@ -532,8 +507,8 @@ { renderer.context.drawImage( texture.baseTexture.source, - texture.crop.x * resolution, - texture.crop.y * resolution, + texture.frame.x * resolution, + texture.frame.y * resolution, width * resolution, height * resolution, dx * renderer.resolution, diff --git a/src/core/display/ObservablePoint.js b/src/core/display/ObservablePoint.js index fd7a04b..958661f 100644 --- a/src/core/display/ObservablePoint.js +++ b/src/core/display/ObservablePoint.js @@ -35,7 +35,7 @@ set: function (value) { this._x = value; - this.transform.dirty = true; + this.transform._versionLocal++; } }, @@ -47,7 +47,7 @@ set: function (value) { this._y = value; - this.transform.dirty = true; + this.transform._versionLocal++; } } }); @@ -63,6 +63,6 @@ { this._x = x || 0; this._y = y || ( (y !== 0) ? this._x : 0 ); - - this.transform.dirty = true; + + this.transform._versionLocal++; }; diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index 61161d0..b86aa2d 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -1,6 +1,7 @@ var math = require('../math'); var ObservablePoint = require('./ObservablePoint'); +var generatorId = 0; /** * The Point object represents a location in a two-dimensional coordinate system, where x represents * the horizontal axis and y represents the vertical axis. @@ -19,13 +20,17 @@ this.scale = new ObservablePoint(this,1, 1); this.pivot = new ObservablePoint(this, 0, 0); this.skew = new ObservablePoint(this, 0,0); - + this.rotation = 0; this._sr = Math.sin(0); this._cr = Math.cos(0); - - this.dirty = true; - this.updated = true; + + this._versionLocal = 0; + this._versionGlobal = 0; + this._dirtyLocal = 0; + this._dirtyParentVersion = -1; + this._dirtyParentId = -1; + this._transformId = ++generatorId; } TransformStatic.prototype.constructor = TransformStatic; @@ -36,21 +41,21 @@ var wt = this.worldTransform; var lt = this.localTransform; - if(this.dirty) + if(this._dirtyLocal !== this._versionLocal || + parentTransform._dirtyParentId !== parentTransform._transformId || + parentTransform._dirtyParentVersion !== parentTransform._versionGlobal ) { - // get the matrix values of the displayobject based on its transform properties.. - lt.a = this._cr * this.scale._x; - lt.b = this._sr * this.scale._x; - lt.c = -this._sr * this.scale._y; - lt.d = this._cr * this.scale._y; - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - } - - this.updated = this.dirty || parentTransform.updated; - - if(this.updated) - { + if(this._dirtyLocal !== this._versionLocal) + { + // get the matrix values of the displayobject based on its transform properties.. + lt.a = this._cr * this.scale._x; + lt.b = this._sr * this.scale._x; + lt.c = -this._sr * this.scale._y; + lt.d = this._cr * this.scale._y; + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._dirtyLocal = this._versionLocal; + } // concat the parent matrix with the objects transform. wt.a = lt.a * pt.a + lt.b * pt.c; wt.b = lt.a * pt.b + lt.b * pt.d; @@ -58,10 +63,12 @@ wt.d = lt.c * pt.b + lt.d * pt.d; wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._dirtyParentId = parentTransform._transformId; + this._dirtyParentVersion = parentTransform._versionGlobal; + this._versionGlobal++; } - - this.dirty = false; -} +}; module.exports = TransformStatic; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js new file mode 100644 index 0000000..1edb988 --- /dev/null +++ b/src/core/math/GroupD8.js @@ -0,0 +1,162 @@ +// Your friendly neighbour https://en.wikipedia.org/wiki/Dihedral_group of order 16 + +var ux = [1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1]; +var uy = [0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1]; +var vx = [0, -1, -1, -1, 0, 1, 1, 1, 0, 1, 1, 1, 0, -1, -1, -1]; +var vy = [1, 1, 0, -1, -1, -1, 0, 1, -1, -1, 0, 1, 1, 1, 0, -1]; +var tempMatrices = []; +var Matrix = require('./Matrix'); + +var mul = []; + +function signum(x) { + if (x < 0) { + return -1; + } + if (x > 0) { + return 1; + } + return 0; +} + +function init() { + for (var i = 0; i < 16; i++) { + var row = []; + mul.push(row); + for (var j = 0; j < 16; j++) { + var _ux = signum(ux[i] * ux[j] + vx[i] * uy[j]); + var _uy = signum(uy[i] * ux[j] + vy[i] * uy[j]); + var _vx = signum(ux[i] * vx[j] + vx[i] * vy[j]); + var _vy = signum(uy[i] * vx[j] + vy[i] * vy[j]); + for (var k = 0; k < 16; k++) { + if (ux[k] === _ux && uy[k] === _uy && vx[k] === _vx && vy[k] === _vy) { + row.push(k); + break; + } + } + } + } + + for (i=0;i<16;i++) { + var mat = new Matrix(); + mat.set(ux[i], uy[i], vx[i], vy[i], 0, 0); + tempMatrices.push(mat); + } +} + +init(); + +/** + * Implements Dihedral Group D_8, see [group D4]{@link http://mathworld.wolfram.com/DihedralGroupD4.html}, D8 is the same but with diagonals + * Used for texture rotations + * Vector xX(i), xY(i) is U-axis of sprite with rotation i + * Vector yY(i), yY(i) is V-axis of sprite with rotation i + * Rotations: 0 grad (0), 90 grad (2), 180 grad (4), 270 grad (6) + * Mirrors: vertical (8), main diagonal (10), horizontal (12), reverse diagonal (14) + * This is the small part of gameofbombs.com portal system. It works. + * @author Ivan @ivanpopelyshev + * + * @namespace PIXI.GroupD8 + */ +var GroupD8 = { + E: 0, + SE: 1, + S: 2, + SW: 3, + W: 4, + NW: 5, + N: 6, + NE: 7, + MIRROR_VERTICAL: 8, + MIRROR_HORIZONTAL: 12, + uX: function (ind) { + return ux[ind]; + }, + uY: function (ind) { + return uy[ind]; + }, + vX: function (ind) { + return vx[ind]; + }, + vY: function (ind) { + return vy[ind]; + }, + inv: function (rotation) { + if (rotation & 8) { + return rotation & 15; + } + return (-rotation) & 7; + }, + add: function (rotationSecond, rotationFirst) { + return mul[rotationSecond][rotationFirst]; + }, + sub: function (rotationSecond, rotationFirst) { + return mul[rotationSecond][GroupD8.inv(rotationFirst)]; + }, + /** + * Adds 180 degrees to rotation. Commutative operation + * @param rotation + * @returns {number} + */ + rotate180: function (rotation) { + return rotation ^ 4; + }, + /** + * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * @param rotation + * @returns {boolean} + */ + isSwapWidthHeight: function(rotation) { + return (rotation & 3) === 2; + }, + byDirection: function (dx, dy) { + if (Math.abs(dx) * 2 <= Math.abs(dy)) { + if (dy >= 0) { + return GroupD8.S; + } + else { + return GroupD8.N; + } + } else if (Math.abs(dy) * 2 <= Math.abs(dx)) { + if (dx > 0) { + return GroupD8.E; + } + else { + return GroupD8.W; + } + } else { + if (dy > 0) { + if (dx > 0) { + return GroupD8.SE; + } + else { + return GroupD8.SW; + } + } + else if (dx > 0) { + return GroupD8.NE; + } + else { + return GroupD8.NW; + } + } + }, + /** + * Helps sprite to compensate texture packer rotation. + * @param matrix {PIXI.Matrix} sprite world matrix + * @param rotation {number} + * @param tx {number|*} sprite anchoring + * @param ty {number|*} sprite anchoring + */ + matrixAppendRotationInv: function (matrix, rotation, tx, ty) { + //Packer used "rotation", we use "inv(rotation)" + var mat = tempMatrices[GroupD8.inv(rotation)]; + tx = tx || 0; + ty = ty || 0; + mat.tx = tx; + mat.ty = ty; + matrix.append(mat); + } +}; + +module.exports = GroupD8; diff --git a/src/core/math/index.js b/src/core/math/index.js index fd0d9be..a43f6e7 100644 --- a/src/core/math/index.js +++ b/src/core/math/index.js @@ -11,6 +11,7 @@ Point: require('./Point'), Matrix: require('./Matrix'), + GroupD8: require('./GroupD8'), Circle: require('./shapes/Circle'), Ellipse: require('./shapes/Ellipse'), diff --git a/src/core/particles/webgl/ParticleRenderer.js b/src/core/particles/webgl/ParticleRenderer.js index 8481c92..0b0abd0 100644 --- a/src/core/particles/webgl/ParticleRenderer.js +++ b/src/core/particles/webgl/ParticleRenderer.js @@ -285,25 +285,25 @@ texture = sprite._texture; sx = sprite.scale.x; sy = sprite.scale.y; + var trim = texture.trim, crop = texture.crop; - if (texture.trim) + if (trim) { - // if the sprite is trimmed then we need to add the extra space before transforming the sprite coords.. - trim = texture.trim; + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - sprite.anchor.x * crop.width; + w0 = w1 + trim.width; - w1 = trim.x - sprite.anchor.x * trim.width; - w0 = w1 + texture.crop.width; + h1 = trim.y - sprite.anchor.y * crop.height; + h0 = h1 + trim.height; - h1 = trim.y - sprite.anchor.y * trim.height; - h0 = h1 + texture.crop.height; } else { - w0 = (texture._frame.width ) * (1-sprite.anchor.x); - w1 = (texture._frame.width ) * -sprite.anchor.x; + w0 = (crop.width ) * (1-sprite.anchor.x); + w1 = (crop.width ) * -sprite.anchor.x; - h0 = texture._frame.height * (1-sprite.anchor.y); - h1 = texture._frame.height * -sprite.anchor.y; + h0 = crop.height * (1-sprite.anchor.y); + h1 = crop.height * -sprite.anchor.y; } array[offset] = w1 * sx; diff --git a/src/core/renderers/canvas/utils/CanvasTinter.js b/src/core/renderers/canvas/utils/CanvasTinter.js index eedf175..8827558 100644 --- a/src/core/renderers/canvas/utils/CanvasTinter.js +++ b/src/core/renderers/canvas/utils/CanvasTinter.js @@ -68,11 +68,7 @@ var resolution = texture.baseTexture.resolution; - var crop = texture.crop.clone(); - crop.x *= resolution; - crop.y *= resolution; - crop.width *= resolution; - crop.height *= resolution; + var crop = texture._frame; canvas.width = crop.width; canvas.height = crop.height; @@ -123,11 +119,7 @@ var resolution = texture.baseTexture.resolution; - var crop = texture.crop.clone(); - crop.x *= resolution; - crop.y *= resolution; - crop.width *= resolution; - crop.height *= resolution; + var crop = texture._frame; canvas.width = crop.width; canvas.height = crop.height; @@ -165,11 +157,7 @@ var resolution = texture.baseTexture.resolution; - var crop = texture.crop.clone(); - crop.x *= resolution; - crop.y *= resolution; - crop.width *= resolution; - crop.height *= resolution; + var crop = texture._frame; canvas.width = crop.width; canvas.height = crop.height; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fcf53cb..1b79373 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -111,12 +111,12 @@ width: { get: function () { - return Math.abs(this.scale.x) * this.texture._frame.width; + return Math.abs(this.scale.x) * this.texture.crop.width; }, set: function (value) { var sign = utils.sign(this.scale.x) || 1; - this.scale.x = sign * value / this.texture._frame.width; + this.scale.x = sign * value / this.texture.crop.width; this._width = value; } }, @@ -130,12 +130,12 @@ height: { get: function () { - return Math.abs(this.scale.y) * this.texture._frame.height; + return Math.abs(this.scale.y) * this.texture.crop.height; }, set: function (value) { var sign = utils.sign(this.scale.y) || 1; - this.scale.y = sign * value / this.texture._frame.height; + this.scale.y = sign * value / this.texture.crop.height; this._height = value; } }, @@ -191,12 +191,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.frame.width; + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.crop.width; } if (this._height) { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.frame.height; + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.crop.height; } }; @@ -207,25 +207,26 @@ a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, vertexData = this.vertexData, w0, w1, h0, h1, - trim = texture.trim; - + trim = texture.trim, + crop = texture.crop; + if (trim) { // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor.x * trim.width; - w0 = w1 + texture.crop.width; + w1 = trim.x - this.anchor.x * crop.width; + w0 = w1 + trim.width; - h1 = trim.y - this.anchor.y * trim.height; - h0 = h1 + texture.crop.height; + h1 = trim.y - this.anchor.y * crop.height; + h0 = h1 + trim.height; } else { - w0 = (texture._frame.width ) * (1-this.anchor.x); - w1 = (texture._frame.width ) * -this.anchor.x; + w0 = (crop.width ) * (1-this.anchor.x); + w1 = (crop.width ) * -this.anchor.x; - h0 = texture._frame.height * (1-this.anchor.y); - h1 = texture._frame.height * -this.anchor.y; + h0 = crop.height * (1-this.anchor.y); + h1 = crop.height * -this.anchor.y; } // xy @@ -244,7 +245,6 @@ vertexData[6] = a * w1 + c * h0 + tx; vertexData[7] = d * h0 + b * w1 + ty; - } /** @@ -262,7 +262,7 @@ // set the vertex data this.caclulateVertices(); } - + renderer.setObjectRenderer(renderer.plugins.sprite); renderer.plugins.sprite.render(this); }; @@ -362,10 +362,10 @@ */ Sprite.prototype.getLocalBounds = function () { - this._bounds.x = -this._texture._frame.width * this.anchor.x; - this._bounds.y = -this._texture._frame.height * this.anchor.y; - this._bounds.width = this._texture._frame.width; - this._bounds.height = this._texture._frame.height; + this._bounds.x = -this._texture.crop.width * this.anchor.x; + this._bounds.y = -this._texture.crop.height * this.anchor.y; + this._bounds.width = this._texture.crop.width; + this._bounds.height = this._texture.crop.height; return this._bounds; }; @@ -379,8 +379,8 @@ { this.worldTransform.applyInverse(point, tempPoint); - var width = this._texture._frame.width; - var height = this._texture._frame.height; + var width = this._texture.crop.width; + var height = this._texture.crop.height; var x1 = -width * this.anchor.x; var y1; @@ -423,8 +423,8 @@ wt = this.worldTransform, dx, dy, - width, - height; + width = texture._frame.width, + height = texture._frame.height; renderer.context.globalAlpha = this.worldAlpha; @@ -435,45 +435,23 @@ renderer.context[renderer.smoothProperty] = smoothingEnabled; } - // If the texture is trimmed we offset by the trim x/y, otherwise we use the frame dimensions - - if(texture.rotate) - { - width = texture.crop.height; - height = texture.crop.width; - - dx = (texture.trim) ? texture.trim.y - this.anchor.y * texture.trim.height : this.anchor.y * -texture._frame.height; - dy = (texture.trim) ? texture.trim.x - this.anchor.x * texture.trim.width : this.anchor.x * -texture._frame.width; - - dx += width; - - wt.tx = dy * wt.a + dx * wt.c + wt.tx; - wt.ty = dy * wt.b + dx * wt.d + wt.ty; - - var temp = wt.a; - wt.a = -wt.c; - wt.c = temp; - - temp = wt.b; - wt.b = -wt.d; - wt.d = temp; - + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - this.anchor.x * texture.crop.width; + dy = texture.trim.height/2 + texture.trim.y - this.anchor.y * texture.crop.height; + } else { + dx = (0.5 - this.anchor.x) * texture.crop.width; + dy = (0.5 - this.anchor.y) * texture.crop.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); // the anchor has already been applied above, so lets set it to zero dx = 0; dy = 0; - } - else - { - width = texture.crop.width; - height = texture.crop.height; - - dx = (texture.trim) ? texture.trim.x - this.anchor.x * texture.trim.width : this.anchor.x * -texture._frame.width; - dy = (texture.trim) ? texture.trim.y - this.anchor.y * texture.trim.height : this.anchor.y * -texture._frame.height; - } - - - + dx -= width/2; + dy -= height/2; // Allow for pixel rounding if (renderer.roundPixels) { @@ -491,7 +469,6 @@ } else { - renderer.context.setTransform( wt.a, wt.b, @@ -500,8 +477,6 @@ wt.tx * renderer.resolution, wt.ty * renderer.resolution ); - - } var resolution = texture.baseTexture.resolution; @@ -532,8 +507,8 @@ { renderer.context.drawImage( texture.baseTexture.source, - texture.crop.x * resolution, - texture.crop.y * resolution, + texture.frame.x * resolution, + texture.frame.y * resolution, width * resolution, height * resolution, dx * renderer.resolution, diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index e43e8b5..00aaa6f 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -22,8 +22,8 @@ * @param baseTexture {PIXI.BaseTexture} The base texture source to create the texture from * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show * @param [crop] {PIXI.Rectangle} The area of original texture - * @param [trim] {PIXI.Rectangle} Trimmed texture rectangle - * @param [rotate] {boolean} indicates whether the texture should be rotated by 90 degrees ( used by texture packer ) + * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture + * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ function Texture(baseTexture, frame, crop, trim, rotate) { @@ -55,15 +55,15 @@ this.baseTexture = baseTexture; /** - * The frame specifies the region of the base texture that this texture uses + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) * * @member {PIXI.Rectangle} - * @private */ this._frame = frame; /** - * The texture trim data. + * This is the trimmed area of original texture, before it was put in atlas * * @member {PIXI.Rectangle} */ @@ -106,20 +106,22 @@ this.height = 0; /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * This is the area of original texture, before it was put in atlas * * @member {PIXI.Rectangle} */ this.crop = crop || frame;//new math.Rectangle(0, 0, 1, 1); - /** - * Indicates whether the texture should be rotated by 90 degrees - * - * @private - * @member {boolean} - */ - this.rotate = !!rotate; + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } if (baseTexture.hasLoaded) { @@ -168,10 +170,7 @@ this.noFrame = false; - this.width = frame.width; - this.height = frame.height; - - if (!this.trim && !this.rotate && (frame.x + frame.width > this.baseTexture.width || frame.y + frame.height > this.baseTexture.height)) + if (frame.x + frame.width > this.baseTexture.width || frame.y + frame.height > this.baseTexture.height) { throw new Error('Texture Error: frame does not fit inside the base Texture dimensions ' + this); } @@ -179,15 +178,9 @@ //this.valid = frame && frame.width && frame.height && this.baseTexture.source && this.baseTexture.hasLoaded; this.valid = frame && frame.width && frame.height && this.baseTexture.hasLoaded; - if (this.trim) - { - this.width = this.trim.width; - this.height = this.trim.height; - this._frame.width = this.trim.width; - this._frame.height = this.trim.height; - } - else - { + if (!this.trim) { + this.width = frame.width; + this.height = frame.height; this.crop = frame; } @@ -196,6 +189,29 @@ this._updateUvs(); } } + }, + /** + * Indicates whether the texture is rotated inside the atlas + * set to 2 to compensate for texture packer rotation + * set to 6 to compensate for spine packer rotation + * can be used to rotate or mirror sprites + * See {@link PIXI.GroupD8} for explanation + * + * @member {number} + */ + rotate: { + get: function () + { + return this._rotate; + }, + set: function (rotate) + { + this._rotate = rotate; + if (this.valid) + { + this._updateUvs(); + } + } } }); @@ -294,7 +310,7 @@ this._uvs = new TextureUvs(); } - this._uvs.set(this.crop, this.baseTexture, this.rotate); + this._uvs.set(this._frame, this.baseTexture, this.rotate); }; /** diff --git a/src/core/display/ObservablePoint.js b/src/core/display/ObservablePoint.js index fd7a04b..958661f 100644 --- a/src/core/display/ObservablePoint.js +++ b/src/core/display/ObservablePoint.js @@ -35,7 +35,7 @@ set: function (value) { this._x = value; - this.transform.dirty = true; + this.transform._versionLocal++; } }, @@ -47,7 +47,7 @@ set: function (value) { this._y = value; - this.transform.dirty = true; + this.transform._versionLocal++; } } }); @@ -63,6 +63,6 @@ { this._x = x || 0; this._y = y || ( (y !== 0) ? this._x : 0 ); - - this.transform.dirty = true; + + this.transform._versionLocal++; }; diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index 61161d0..b86aa2d 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -1,6 +1,7 @@ var math = require('../math'); var ObservablePoint = require('./ObservablePoint'); +var generatorId = 0; /** * The Point object represents a location in a two-dimensional coordinate system, where x represents * the horizontal axis and y represents the vertical axis. @@ -19,13 +20,17 @@ this.scale = new ObservablePoint(this,1, 1); this.pivot = new ObservablePoint(this, 0, 0); this.skew = new ObservablePoint(this, 0,0); - + this.rotation = 0; this._sr = Math.sin(0); this._cr = Math.cos(0); - - this.dirty = true; - this.updated = true; + + this._versionLocal = 0; + this._versionGlobal = 0; + this._dirtyLocal = 0; + this._dirtyParentVersion = -1; + this._dirtyParentId = -1; + this._transformId = ++generatorId; } TransformStatic.prototype.constructor = TransformStatic; @@ -36,21 +41,21 @@ var wt = this.worldTransform; var lt = this.localTransform; - if(this.dirty) + if(this._dirtyLocal !== this._versionLocal || + parentTransform._dirtyParentId !== parentTransform._transformId || + parentTransform._dirtyParentVersion !== parentTransform._versionGlobal ) { - // get the matrix values of the displayobject based on its transform properties.. - lt.a = this._cr * this.scale._x; - lt.b = this._sr * this.scale._x; - lt.c = -this._sr * this.scale._y; - lt.d = this._cr * this.scale._y; - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - } - - this.updated = this.dirty || parentTransform.updated; - - if(this.updated) - { + if(this._dirtyLocal !== this._versionLocal) + { + // get the matrix values of the displayobject based on its transform properties.. + lt.a = this._cr * this.scale._x; + lt.b = this._sr * this.scale._x; + lt.c = -this._sr * this.scale._y; + lt.d = this._cr * this.scale._y; + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._dirtyLocal = this._versionLocal; + } // concat the parent matrix with the objects transform. wt.a = lt.a * pt.a + lt.b * pt.c; wt.b = lt.a * pt.b + lt.b * pt.d; @@ -58,10 +63,12 @@ wt.d = lt.c * pt.b + lt.d * pt.d; wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._dirtyParentId = parentTransform._transformId; + this._dirtyParentVersion = parentTransform._versionGlobal; + this._versionGlobal++; } - - this.dirty = false; -} +}; module.exports = TransformStatic; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js new file mode 100644 index 0000000..1edb988 --- /dev/null +++ b/src/core/math/GroupD8.js @@ -0,0 +1,162 @@ +// Your friendly neighbour https://en.wikipedia.org/wiki/Dihedral_group of order 16 + +var ux = [1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1]; +var uy = [0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1]; +var vx = [0, -1, -1, -1, 0, 1, 1, 1, 0, 1, 1, 1, 0, -1, -1, -1]; +var vy = [1, 1, 0, -1, -1, -1, 0, 1, -1, -1, 0, 1, 1, 1, 0, -1]; +var tempMatrices = []; +var Matrix = require('./Matrix'); + +var mul = []; + +function signum(x) { + if (x < 0) { + return -1; + } + if (x > 0) { + return 1; + } + return 0; +} + +function init() { + for (var i = 0; i < 16; i++) { + var row = []; + mul.push(row); + for (var j = 0; j < 16; j++) { + var _ux = signum(ux[i] * ux[j] + vx[i] * uy[j]); + var _uy = signum(uy[i] * ux[j] + vy[i] * uy[j]); + var _vx = signum(ux[i] * vx[j] + vx[i] * vy[j]); + var _vy = signum(uy[i] * vx[j] + vy[i] * vy[j]); + for (var k = 0; k < 16; k++) { + if (ux[k] === _ux && uy[k] === _uy && vx[k] === _vx && vy[k] === _vy) { + row.push(k); + break; + } + } + } + } + + for (i=0;i<16;i++) { + var mat = new Matrix(); + mat.set(ux[i], uy[i], vx[i], vy[i], 0, 0); + tempMatrices.push(mat); + } +} + +init(); + +/** + * Implements Dihedral Group D_8, see [group D4]{@link http://mathworld.wolfram.com/DihedralGroupD4.html}, D8 is the same but with diagonals + * Used for texture rotations + * Vector xX(i), xY(i) is U-axis of sprite with rotation i + * Vector yY(i), yY(i) is V-axis of sprite with rotation i + * Rotations: 0 grad (0), 90 grad (2), 180 grad (4), 270 grad (6) + * Mirrors: vertical (8), main diagonal (10), horizontal (12), reverse diagonal (14) + * This is the small part of gameofbombs.com portal system. It works. + * @author Ivan @ivanpopelyshev + * + * @namespace PIXI.GroupD8 + */ +var GroupD8 = { + E: 0, + SE: 1, + S: 2, + SW: 3, + W: 4, + NW: 5, + N: 6, + NE: 7, + MIRROR_VERTICAL: 8, + MIRROR_HORIZONTAL: 12, + uX: function (ind) { + return ux[ind]; + }, + uY: function (ind) { + return uy[ind]; + }, + vX: function (ind) { + return vx[ind]; + }, + vY: function (ind) { + return vy[ind]; + }, + inv: function (rotation) { + if (rotation & 8) { + return rotation & 15; + } + return (-rotation) & 7; + }, + add: function (rotationSecond, rotationFirst) { + return mul[rotationSecond][rotationFirst]; + }, + sub: function (rotationSecond, rotationFirst) { + return mul[rotationSecond][GroupD8.inv(rotationFirst)]; + }, + /** + * Adds 180 degrees to rotation. Commutative operation + * @param rotation + * @returns {number} + */ + rotate180: function (rotation) { + return rotation ^ 4; + }, + /** + * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * @param rotation + * @returns {boolean} + */ + isSwapWidthHeight: function(rotation) { + return (rotation & 3) === 2; + }, + byDirection: function (dx, dy) { + if (Math.abs(dx) * 2 <= Math.abs(dy)) { + if (dy >= 0) { + return GroupD8.S; + } + else { + return GroupD8.N; + } + } else if (Math.abs(dy) * 2 <= Math.abs(dx)) { + if (dx > 0) { + return GroupD8.E; + } + else { + return GroupD8.W; + } + } else { + if (dy > 0) { + if (dx > 0) { + return GroupD8.SE; + } + else { + return GroupD8.SW; + } + } + else if (dx > 0) { + return GroupD8.NE; + } + else { + return GroupD8.NW; + } + } + }, + /** + * Helps sprite to compensate texture packer rotation. + * @param matrix {PIXI.Matrix} sprite world matrix + * @param rotation {number} + * @param tx {number|*} sprite anchoring + * @param ty {number|*} sprite anchoring + */ + matrixAppendRotationInv: function (matrix, rotation, tx, ty) { + //Packer used "rotation", we use "inv(rotation)" + var mat = tempMatrices[GroupD8.inv(rotation)]; + tx = tx || 0; + ty = ty || 0; + mat.tx = tx; + mat.ty = ty; + matrix.append(mat); + } +}; + +module.exports = GroupD8; diff --git a/src/core/math/index.js b/src/core/math/index.js index fd0d9be..a43f6e7 100644 --- a/src/core/math/index.js +++ b/src/core/math/index.js @@ -11,6 +11,7 @@ Point: require('./Point'), Matrix: require('./Matrix'), + GroupD8: require('./GroupD8'), Circle: require('./shapes/Circle'), Ellipse: require('./shapes/Ellipse'), diff --git a/src/core/particles/webgl/ParticleRenderer.js b/src/core/particles/webgl/ParticleRenderer.js index 8481c92..0b0abd0 100644 --- a/src/core/particles/webgl/ParticleRenderer.js +++ b/src/core/particles/webgl/ParticleRenderer.js @@ -285,25 +285,25 @@ texture = sprite._texture; sx = sprite.scale.x; sy = sprite.scale.y; + var trim = texture.trim, crop = texture.crop; - if (texture.trim) + if (trim) { - // if the sprite is trimmed then we need to add the extra space before transforming the sprite coords.. - trim = texture.trim; + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - sprite.anchor.x * crop.width; + w0 = w1 + trim.width; - w1 = trim.x - sprite.anchor.x * trim.width; - w0 = w1 + texture.crop.width; + h1 = trim.y - sprite.anchor.y * crop.height; + h0 = h1 + trim.height; - h1 = trim.y - sprite.anchor.y * trim.height; - h0 = h1 + texture.crop.height; } else { - w0 = (texture._frame.width ) * (1-sprite.anchor.x); - w1 = (texture._frame.width ) * -sprite.anchor.x; + w0 = (crop.width ) * (1-sprite.anchor.x); + w1 = (crop.width ) * -sprite.anchor.x; - h0 = texture._frame.height * (1-sprite.anchor.y); - h1 = texture._frame.height * -sprite.anchor.y; + h0 = crop.height * (1-sprite.anchor.y); + h1 = crop.height * -sprite.anchor.y; } array[offset] = w1 * sx; diff --git a/src/core/renderers/canvas/utils/CanvasTinter.js b/src/core/renderers/canvas/utils/CanvasTinter.js index eedf175..8827558 100644 --- a/src/core/renderers/canvas/utils/CanvasTinter.js +++ b/src/core/renderers/canvas/utils/CanvasTinter.js @@ -68,11 +68,7 @@ var resolution = texture.baseTexture.resolution; - var crop = texture.crop.clone(); - crop.x *= resolution; - crop.y *= resolution; - crop.width *= resolution; - crop.height *= resolution; + var crop = texture._frame; canvas.width = crop.width; canvas.height = crop.height; @@ -123,11 +119,7 @@ var resolution = texture.baseTexture.resolution; - var crop = texture.crop.clone(); - crop.x *= resolution; - crop.y *= resolution; - crop.width *= resolution; - crop.height *= resolution; + var crop = texture._frame; canvas.width = crop.width; canvas.height = crop.height; @@ -165,11 +157,7 @@ var resolution = texture.baseTexture.resolution; - var crop = texture.crop.clone(); - crop.x *= resolution; - crop.y *= resolution; - crop.width *= resolution; - crop.height *= resolution; + var crop = texture._frame; canvas.width = crop.width; canvas.height = crop.height; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fcf53cb..1b79373 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -111,12 +111,12 @@ width: { get: function () { - return Math.abs(this.scale.x) * this.texture._frame.width; + return Math.abs(this.scale.x) * this.texture.crop.width; }, set: function (value) { var sign = utils.sign(this.scale.x) || 1; - this.scale.x = sign * value / this.texture._frame.width; + this.scale.x = sign * value / this.texture.crop.width; this._width = value; } }, @@ -130,12 +130,12 @@ height: { get: function () { - return Math.abs(this.scale.y) * this.texture._frame.height; + return Math.abs(this.scale.y) * this.texture.crop.height; }, set: function (value) { var sign = utils.sign(this.scale.y) || 1; - this.scale.y = sign * value / this.texture._frame.height; + this.scale.y = sign * value / this.texture.crop.height; this._height = value; } }, @@ -191,12 +191,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.frame.width; + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.crop.width; } if (this._height) { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.frame.height; + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.crop.height; } }; @@ -207,25 +207,26 @@ a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, vertexData = this.vertexData, w0, w1, h0, h1, - trim = texture.trim; - + trim = texture.trim, + crop = texture.crop; + if (trim) { // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor.x * trim.width; - w0 = w1 + texture.crop.width; + w1 = trim.x - this.anchor.x * crop.width; + w0 = w1 + trim.width; - h1 = trim.y - this.anchor.y * trim.height; - h0 = h1 + texture.crop.height; + h1 = trim.y - this.anchor.y * crop.height; + h0 = h1 + trim.height; } else { - w0 = (texture._frame.width ) * (1-this.anchor.x); - w1 = (texture._frame.width ) * -this.anchor.x; + w0 = (crop.width ) * (1-this.anchor.x); + w1 = (crop.width ) * -this.anchor.x; - h0 = texture._frame.height * (1-this.anchor.y); - h1 = texture._frame.height * -this.anchor.y; + h0 = crop.height * (1-this.anchor.y); + h1 = crop.height * -this.anchor.y; } // xy @@ -244,7 +245,6 @@ vertexData[6] = a * w1 + c * h0 + tx; vertexData[7] = d * h0 + b * w1 + ty; - } /** @@ -262,7 +262,7 @@ // set the vertex data this.caclulateVertices(); } - + renderer.setObjectRenderer(renderer.plugins.sprite); renderer.plugins.sprite.render(this); }; @@ -362,10 +362,10 @@ */ Sprite.prototype.getLocalBounds = function () { - this._bounds.x = -this._texture._frame.width * this.anchor.x; - this._bounds.y = -this._texture._frame.height * this.anchor.y; - this._bounds.width = this._texture._frame.width; - this._bounds.height = this._texture._frame.height; + this._bounds.x = -this._texture.crop.width * this.anchor.x; + this._bounds.y = -this._texture.crop.height * this.anchor.y; + this._bounds.width = this._texture.crop.width; + this._bounds.height = this._texture.crop.height; return this._bounds; }; @@ -379,8 +379,8 @@ { this.worldTransform.applyInverse(point, tempPoint); - var width = this._texture._frame.width; - var height = this._texture._frame.height; + var width = this._texture.crop.width; + var height = this._texture.crop.height; var x1 = -width * this.anchor.x; var y1; @@ -423,8 +423,8 @@ wt = this.worldTransform, dx, dy, - width, - height; + width = texture._frame.width, + height = texture._frame.height; renderer.context.globalAlpha = this.worldAlpha; @@ -435,45 +435,23 @@ renderer.context[renderer.smoothProperty] = smoothingEnabled; } - // If the texture is trimmed we offset by the trim x/y, otherwise we use the frame dimensions - - if(texture.rotate) - { - width = texture.crop.height; - height = texture.crop.width; - - dx = (texture.trim) ? texture.trim.y - this.anchor.y * texture.trim.height : this.anchor.y * -texture._frame.height; - dy = (texture.trim) ? texture.trim.x - this.anchor.x * texture.trim.width : this.anchor.x * -texture._frame.width; - - dx += width; - - wt.tx = dy * wt.a + dx * wt.c + wt.tx; - wt.ty = dy * wt.b + dx * wt.d + wt.ty; - - var temp = wt.a; - wt.a = -wt.c; - wt.c = temp; - - temp = wt.b; - wt.b = -wt.d; - wt.d = temp; - + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - this.anchor.x * texture.crop.width; + dy = texture.trim.height/2 + texture.trim.y - this.anchor.y * texture.crop.height; + } else { + dx = (0.5 - this.anchor.x) * texture.crop.width; + dy = (0.5 - this.anchor.y) * texture.crop.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); // the anchor has already been applied above, so lets set it to zero dx = 0; dy = 0; - } - else - { - width = texture.crop.width; - height = texture.crop.height; - - dx = (texture.trim) ? texture.trim.x - this.anchor.x * texture.trim.width : this.anchor.x * -texture._frame.width; - dy = (texture.trim) ? texture.trim.y - this.anchor.y * texture.trim.height : this.anchor.y * -texture._frame.height; - } - - - + dx -= width/2; + dy -= height/2; // Allow for pixel rounding if (renderer.roundPixels) { @@ -491,7 +469,6 @@ } else { - renderer.context.setTransform( wt.a, wt.b, @@ -500,8 +477,6 @@ wt.tx * renderer.resolution, wt.ty * renderer.resolution ); - - } var resolution = texture.baseTexture.resolution; @@ -532,8 +507,8 @@ { renderer.context.drawImage( texture.baseTexture.source, - texture.crop.x * resolution, - texture.crop.y * resolution, + texture.frame.x * resolution, + texture.frame.y * resolution, width * resolution, height * resolution, dx * renderer.resolution, diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index e43e8b5..00aaa6f 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -22,8 +22,8 @@ * @param baseTexture {PIXI.BaseTexture} The base texture source to create the texture from * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show * @param [crop] {PIXI.Rectangle} The area of original texture - * @param [trim] {PIXI.Rectangle} Trimmed texture rectangle - * @param [rotate] {boolean} indicates whether the texture should be rotated by 90 degrees ( used by texture packer ) + * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture + * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ function Texture(baseTexture, frame, crop, trim, rotate) { @@ -55,15 +55,15 @@ this.baseTexture = baseTexture; /** - * The frame specifies the region of the base texture that this texture uses + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) * * @member {PIXI.Rectangle} - * @private */ this._frame = frame; /** - * The texture trim data. + * This is the trimmed area of original texture, before it was put in atlas * * @member {PIXI.Rectangle} */ @@ -106,20 +106,22 @@ this.height = 0; /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * This is the area of original texture, before it was put in atlas * * @member {PIXI.Rectangle} */ this.crop = crop || frame;//new math.Rectangle(0, 0, 1, 1); - /** - * Indicates whether the texture should be rotated by 90 degrees - * - * @private - * @member {boolean} - */ - this.rotate = !!rotate; + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } if (baseTexture.hasLoaded) { @@ -168,10 +170,7 @@ this.noFrame = false; - this.width = frame.width; - this.height = frame.height; - - if (!this.trim && !this.rotate && (frame.x + frame.width > this.baseTexture.width || frame.y + frame.height > this.baseTexture.height)) + if (frame.x + frame.width > this.baseTexture.width || frame.y + frame.height > this.baseTexture.height) { throw new Error('Texture Error: frame does not fit inside the base Texture dimensions ' + this); } @@ -179,15 +178,9 @@ //this.valid = frame && frame.width && frame.height && this.baseTexture.source && this.baseTexture.hasLoaded; this.valid = frame && frame.width && frame.height && this.baseTexture.hasLoaded; - if (this.trim) - { - this.width = this.trim.width; - this.height = this.trim.height; - this._frame.width = this.trim.width; - this._frame.height = this.trim.height; - } - else - { + if (!this.trim) { + this.width = frame.width; + this.height = frame.height; this.crop = frame; } @@ -196,6 +189,29 @@ this._updateUvs(); } } + }, + /** + * Indicates whether the texture is rotated inside the atlas + * set to 2 to compensate for texture packer rotation + * set to 6 to compensate for spine packer rotation + * can be used to rotate or mirror sprites + * See {@link PIXI.GroupD8} for explanation + * + * @member {number} + */ + rotate: { + get: function () + { + return this._rotate; + }, + set: function (rotate) + { + this._rotate = rotate; + if (this.valid) + { + this._updateUvs(); + } + } } }); @@ -294,7 +310,7 @@ this._uvs = new TextureUvs(); } - this._uvs.set(this.crop, this.baseTexture, this.rotate); + this._uvs.set(this._frame, this.baseTexture, this.rotate); }; /** diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index c5e4564..11bed5d 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -30,11 +30,13 @@ module.exports = TextureUvs; +var GroupD8 = require('../math/GroupD8'); + /** * Sets the texture Uvs based on the given frame information * @param frame {PIXI.Rectangle} * @param baseFrame {PIXI.Rectangle} - * @param rotate {boolean} Whether or not the frame is rotated + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} * @private */ TextureUvs.prototype.set = function (frame, baseFrame, rotate) @@ -44,17 +46,24 @@ if(rotate) { - this.x0 = (frame.x + frame.height) / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.height) / tw; - this.y1 = (frame.y + frame.width) / th; - - this.x2 = frame.x / tw; - this.y2 = (frame.y + frame.width) / th; - - this.x3 = frame.x / tw; - this.y3 = frame.y / th; + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); } else { diff --git a/src/core/display/ObservablePoint.js b/src/core/display/ObservablePoint.js index fd7a04b..958661f 100644 --- a/src/core/display/ObservablePoint.js +++ b/src/core/display/ObservablePoint.js @@ -35,7 +35,7 @@ set: function (value) { this._x = value; - this.transform.dirty = true; + this.transform._versionLocal++; } }, @@ -47,7 +47,7 @@ set: function (value) { this._y = value; - this.transform.dirty = true; + this.transform._versionLocal++; } } }); @@ -63,6 +63,6 @@ { this._x = x || 0; this._y = y || ( (y !== 0) ? this._x : 0 ); - - this.transform.dirty = true; + + this.transform._versionLocal++; }; diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index 61161d0..b86aa2d 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -1,6 +1,7 @@ var math = require('../math'); var ObservablePoint = require('./ObservablePoint'); +var generatorId = 0; /** * The Point object represents a location in a two-dimensional coordinate system, where x represents * the horizontal axis and y represents the vertical axis. @@ -19,13 +20,17 @@ this.scale = new ObservablePoint(this,1, 1); this.pivot = new ObservablePoint(this, 0, 0); this.skew = new ObservablePoint(this, 0,0); - + this.rotation = 0; this._sr = Math.sin(0); this._cr = Math.cos(0); - - this.dirty = true; - this.updated = true; + + this._versionLocal = 0; + this._versionGlobal = 0; + this._dirtyLocal = 0; + this._dirtyParentVersion = -1; + this._dirtyParentId = -1; + this._transformId = ++generatorId; } TransformStatic.prototype.constructor = TransformStatic; @@ -36,21 +41,21 @@ var wt = this.worldTransform; var lt = this.localTransform; - if(this.dirty) + if(this._dirtyLocal !== this._versionLocal || + parentTransform._dirtyParentId !== parentTransform._transformId || + parentTransform._dirtyParentVersion !== parentTransform._versionGlobal ) { - // get the matrix values of the displayobject based on its transform properties.. - lt.a = this._cr * this.scale._x; - lt.b = this._sr * this.scale._x; - lt.c = -this._sr * this.scale._y; - lt.d = this._cr * this.scale._y; - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - } - - this.updated = this.dirty || parentTransform.updated; - - if(this.updated) - { + if(this._dirtyLocal !== this._versionLocal) + { + // get the matrix values of the displayobject based on its transform properties.. + lt.a = this._cr * this.scale._x; + lt.b = this._sr * this.scale._x; + lt.c = -this._sr * this.scale._y; + lt.d = this._cr * this.scale._y; + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._dirtyLocal = this._versionLocal; + } // concat the parent matrix with the objects transform. wt.a = lt.a * pt.a + lt.b * pt.c; wt.b = lt.a * pt.b + lt.b * pt.d; @@ -58,10 +63,12 @@ wt.d = lt.c * pt.b + lt.d * pt.d; wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._dirtyParentId = parentTransform._transformId; + this._dirtyParentVersion = parentTransform._versionGlobal; + this._versionGlobal++; } - - this.dirty = false; -} +}; module.exports = TransformStatic; diff --git a/src/core/math/GroupD8.js b/src/core/math/GroupD8.js new file mode 100644 index 0000000..1edb988 --- /dev/null +++ b/src/core/math/GroupD8.js @@ -0,0 +1,162 @@ +// Your friendly neighbour https://en.wikipedia.org/wiki/Dihedral_group of order 16 + +var ux = [1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1]; +var uy = [0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1]; +var vx = [0, -1, -1, -1, 0, 1, 1, 1, 0, 1, 1, 1, 0, -1, -1, -1]; +var vy = [1, 1, 0, -1, -1, -1, 0, 1, -1, -1, 0, 1, 1, 1, 0, -1]; +var tempMatrices = []; +var Matrix = require('./Matrix'); + +var mul = []; + +function signum(x) { + if (x < 0) { + return -1; + } + if (x > 0) { + return 1; + } + return 0; +} + +function init() { + for (var i = 0; i < 16; i++) { + var row = []; + mul.push(row); + for (var j = 0; j < 16; j++) { + var _ux = signum(ux[i] * ux[j] + vx[i] * uy[j]); + var _uy = signum(uy[i] * ux[j] + vy[i] * uy[j]); + var _vx = signum(ux[i] * vx[j] + vx[i] * vy[j]); + var _vy = signum(uy[i] * vx[j] + vy[i] * vy[j]); + for (var k = 0; k < 16; k++) { + if (ux[k] === _ux && uy[k] === _uy && vx[k] === _vx && vy[k] === _vy) { + row.push(k); + break; + } + } + } + } + + for (i=0;i<16;i++) { + var mat = new Matrix(); + mat.set(ux[i], uy[i], vx[i], vy[i], 0, 0); + tempMatrices.push(mat); + } +} + +init(); + +/** + * Implements Dihedral Group D_8, see [group D4]{@link http://mathworld.wolfram.com/DihedralGroupD4.html}, D8 is the same but with diagonals + * Used for texture rotations + * Vector xX(i), xY(i) is U-axis of sprite with rotation i + * Vector yY(i), yY(i) is V-axis of sprite with rotation i + * Rotations: 0 grad (0), 90 grad (2), 180 grad (4), 270 grad (6) + * Mirrors: vertical (8), main diagonal (10), horizontal (12), reverse diagonal (14) + * This is the small part of gameofbombs.com portal system. It works. + * @author Ivan @ivanpopelyshev + * + * @namespace PIXI.GroupD8 + */ +var GroupD8 = { + E: 0, + SE: 1, + S: 2, + SW: 3, + W: 4, + NW: 5, + N: 6, + NE: 7, + MIRROR_VERTICAL: 8, + MIRROR_HORIZONTAL: 12, + uX: function (ind) { + return ux[ind]; + }, + uY: function (ind) { + return uy[ind]; + }, + vX: function (ind) { + return vx[ind]; + }, + vY: function (ind) { + return vy[ind]; + }, + inv: function (rotation) { + if (rotation & 8) { + return rotation & 15; + } + return (-rotation) & 7; + }, + add: function (rotationSecond, rotationFirst) { + return mul[rotationSecond][rotationFirst]; + }, + sub: function (rotationSecond, rotationFirst) { + return mul[rotationSecond][GroupD8.inv(rotationFirst)]; + }, + /** + * Adds 180 degrees to rotation. Commutative operation + * @param rotation + * @returns {number} + */ + rotate180: function (rotation) { + return rotation ^ 4; + }, + /** + * I dont know why sometimes width and heights needs to be swapped. We'll fix it later. + * @param rotation + * @returns {boolean} + */ + isSwapWidthHeight: function(rotation) { + return (rotation & 3) === 2; + }, + byDirection: function (dx, dy) { + if (Math.abs(dx) * 2 <= Math.abs(dy)) { + if (dy >= 0) { + return GroupD8.S; + } + else { + return GroupD8.N; + } + } else if (Math.abs(dy) * 2 <= Math.abs(dx)) { + if (dx > 0) { + return GroupD8.E; + } + else { + return GroupD8.W; + } + } else { + if (dy > 0) { + if (dx > 0) { + return GroupD8.SE; + } + else { + return GroupD8.SW; + } + } + else if (dx > 0) { + return GroupD8.NE; + } + else { + return GroupD8.NW; + } + } + }, + /** + * Helps sprite to compensate texture packer rotation. + * @param matrix {PIXI.Matrix} sprite world matrix + * @param rotation {number} + * @param tx {number|*} sprite anchoring + * @param ty {number|*} sprite anchoring + */ + matrixAppendRotationInv: function (matrix, rotation, tx, ty) { + //Packer used "rotation", we use "inv(rotation)" + var mat = tempMatrices[GroupD8.inv(rotation)]; + tx = tx || 0; + ty = ty || 0; + mat.tx = tx; + mat.ty = ty; + matrix.append(mat); + } +}; + +module.exports = GroupD8; diff --git a/src/core/math/index.js b/src/core/math/index.js index fd0d9be..a43f6e7 100644 --- a/src/core/math/index.js +++ b/src/core/math/index.js @@ -11,6 +11,7 @@ Point: require('./Point'), Matrix: require('./Matrix'), + GroupD8: require('./GroupD8'), Circle: require('./shapes/Circle'), Ellipse: require('./shapes/Ellipse'), diff --git a/src/core/particles/webgl/ParticleRenderer.js b/src/core/particles/webgl/ParticleRenderer.js index 8481c92..0b0abd0 100644 --- a/src/core/particles/webgl/ParticleRenderer.js +++ b/src/core/particles/webgl/ParticleRenderer.js @@ -285,25 +285,25 @@ texture = sprite._texture; sx = sprite.scale.x; sy = sprite.scale.y; + var trim = texture.trim, crop = texture.crop; - if (texture.trim) + if (trim) { - // if the sprite is trimmed then we need to add the extra space before transforming the sprite coords.. - trim = texture.trim; + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - sprite.anchor.x * crop.width; + w0 = w1 + trim.width; - w1 = trim.x - sprite.anchor.x * trim.width; - w0 = w1 + texture.crop.width; + h1 = trim.y - sprite.anchor.y * crop.height; + h0 = h1 + trim.height; - h1 = trim.y - sprite.anchor.y * trim.height; - h0 = h1 + texture.crop.height; } else { - w0 = (texture._frame.width ) * (1-sprite.anchor.x); - w1 = (texture._frame.width ) * -sprite.anchor.x; + w0 = (crop.width ) * (1-sprite.anchor.x); + w1 = (crop.width ) * -sprite.anchor.x; - h0 = texture._frame.height * (1-sprite.anchor.y); - h1 = texture._frame.height * -sprite.anchor.y; + h0 = crop.height * (1-sprite.anchor.y); + h1 = crop.height * -sprite.anchor.y; } array[offset] = w1 * sx; diff --git a/src/core/renderers/canvas/utils/CanvasTinter.js b/src/core/renderers/canvas/utils/CanvasTinter.js index eedf175..8827558 100644 --- a/src/core/renderers/canvas/utils/CanvasTinter.js +++ b/src/core/renderers/canvas/utils/CanvasTinter.js @@ -68,11 +68,7 @@ var resolution = texture.baseTexture.resolution; - var crop = texture.crop.clone(); - crop.x *= resolution; - crop.y *= resolution; - crop.width *= resolution; - crop.height *= resolution; + var crop = texture._frame; canvas.width = crop.width; canvas.height = crop.height; @@ -123,11 +119,7 @@ var resolution = texture.baseTexture.resolution; - var crop = texture.crop.clone(); - crop.x *= resolution; - crop.y *= resolution; - crop.width *= resolution; - crop.height *= resolution; + var crop = texture._frame; canvas.width = crop.width; canvas.height = crop.height; @@ -165,11 +157,7 @@ var resolution = texture.baseTexture.resolution; - var crop = texture.crop.clone(); - crop.x *= resolution; - crop.y *= resolution; - crop.width *= resolution; - crop.height *= resolution; + var crop = texture._frame; canvas.width = crop.width; canvas.height = crop.height; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fcf53cb..1b79373 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -111,12 +111,12 @@ width: { get: function () { - return Math.abs(this.scale.x) * this.texture._frame.width; + return Math.abs(this.scale.x) * this.texture.crop.width; }, set: function (value) { var sign = utils.sign(this.scale.x) || 1; - this.scale.x = sign * value / this.texture._frame.width; + this.scale.x = sign * value / this.texture.crop.width; this._width = value; } }, @@ -130,12 +130,12 @@ height: { get: function () { - return Math.abs(this.scale.y) * this.texture._frame.height; + return Math.abs(this.scale.y) * this.texture.crop.height; }, set: function (value) { var sign = utils.sign(this.scale.y) || 1; - this.scale.y = sign * value / this.texture._frame.height; + this.scale.y = sign * value / this.texture.crop.height; this._height = value; } }, @@ -191,12 +191,12 @@ // so if _width is 0 then width was not set.. if (this._width) { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.frame.width; + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.crop.width; } if (this._height) { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.frame.height; + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.crop.height; } }; @@ -207,25 +207,26 @@ a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, vertexData = this.vertexData, w0, w1, h0, h1, - trim = texture.trim; - + trim = texture.trim, + crop = texture.crop; + if (trim) { // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor.x * trim.width; - w0 = w1 + texture.crop.width; + w1 = trim.x - this.anchor.x * crop.width; + w0 = w1 + trim.width; - h1 = trim.y - this.anchor.y * trim.height; - h0 = h1 + texture.crop.height; + h1 = trim.y - this.anchor.y * crop.height; + h0 = h1 + trim.height; } else { - w0 = (texture._frame.width ) * (1-this.anchor.x); - w1 = (texture._frame.width ) * -this.anchor.x; + w0 = (crop.width ) * (1-this.anchor.x); + w1 = (crop.width ) * -this.anchor.x; - h0 = texture._frame.height * (1-this.anchor.y); - h1 = texture._frame.height * -this.anchor.y; + h0 = crop.height * (1-this.anchor.y); + h1 = crop.height * -this.anchor.y; } // xy @@ -244,7 +245,6 @@ vertexData[6] = a * w1 + c * h0 + tx; vertexData[7] = d * h0 + b * w1 + ty; - } /** @@ -262,7 +262,7 @@ // set the vertex data this.caclulateVertices(); } - + renderer.setObjectRenderer(renderer.plugins.sprite); renderer.plugins.sprite.render(this); }; @@ -362,10 +362,10 @@ */ Sprite.prototype.getLocalBounds = function () { - this._bounds.x = -this._texture._frame.width * this.anchor.x; - this._bounds.y = -this._texture._frame.height * this.anchor.y; - this._bounds.width = this._texture._frame.width; - this._bounds.height = this._texture._frame.height; + this._bounds.x = -this._texture.crop.width * this.anchor.x; + this._bounds.y = -this._texture.crop.height * this.anchor.y; + this._bounds.width = this._texture.crop.width; + this._bounds.height = this._texture.crop.height; return this._bounds; }; @@ -379,8 +379,8 @@ { this.worldTransform.applyInverse(point, tempPoint); - var width = this._texture._frame.width; - var height = this._texture._frame.height; + var width = this._texture.crop.width; + var height = this._texture.crop.height; var x1 = -width * this.anchor.x; var y1; @@ -423,8 +423,8 @@ wt = this.worldTransform, dx, dy, - width, - height; + width = texture._frame.width, + height = texture._frame.height; renderer.context.globalAlpha = this.worldAlpha; @@ -435,45 +435,23 @@ renderer.context[renderer.smoothProperty] = smoothingEnabled; } - // If the texture is trimmed we offset by the trim x/y, otherwise we use the frame dimensions - - if(texture.rotate) - { - width = texture.crop.height; - height = texture.crop.width; - - dx = (texture.trim) ? texture.trim.y - this.anchor.y * texture.trim.height : this.anchor.y * -texture._frame.height; - dy = (texture.trim) ? texture.trim.x - this.anchor.x * texture.trim.width : this.anchor.x * -texture._frame.width; - - dx += width; - - wt.tx = dy * wt.a + dx * wt.c + wt.tx; - wt.ty = dy * wt.b + dx * wt.d + wt.ty; - - var temp = wt.a; - wt.a = -wt.c; - wt.c = temp; - - temp = wt.b; - wt.b = -wt.d; - wt.d = temp; - + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - this.anchor.x * texture.crop.width; + dy = texture.trim.height/2 + texture.trim.y - this.anchor.y * texture.crop.height; + } else { + dx = (0.5 - this.anchor.x) * texture.crop.width; + dy = (0.5 - this.anchor.y) * texture.crop.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); // the anchor has already been applied above, so lets set it to zero dx = 0; dy = 0; - } - else - { - width = texture.crop.width; - height = texture.crop.height; - - dx = (texture.trim) ? texture.trim.x - this.anchor.x * texture.trim.width : this.anchor.x * -texture._frame.width; - dy = (texture.trim) ? texture.trim.y - this.anchor.y * texture.trim.height : this.anchor.y * -texture._frame.height; - } - - - + dx -= width/2; + dy -= height/2; // Allow for pixel rounding if (renderer.roundPixels) { @@ -491,7 +469,6 @@ } else { - renderer.context.setTransform( wt.a, wt.b, @@ -500,8 +477,6 @@ wt.tx * renderer.resolution, wt.ty * renderer.resolution ); - - } var resolution = texture.baseTexture.resolution; @@ -532,8 +507,8 @@ { renderer.context.drawImage( texture.baseTexture.source, - texture.crop.x * resolution, - texture.crop.y * resolution, + texture.frame.x * resolution, + texture.frame.y * resolution, width * resolution, height * resolution, dx * renderer.resolution, diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index e43e8b5..00aaa6f 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -22,8 +22,8 @@ * @param baseTexture {PIXI.BaseTexture} The base texture source to create the texture from * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show * @param [crop] {PIXI.Rectangle} The area of original texture - * @param [trim] {PIXI.Rectangle} Trimmed texture rectangle - * @param [rotate] {boolean} indicates whether the texture should be rotated by 90 degrees ( used by texture packer ) + * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture + * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ function Texture(baseTexture, frame, crop, trim, rotate) { @@ -55,15 +55,15 @@ this.baseTexture = baseTexture; /** - * The frame specifies the region of the base texture that this texture uses + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) * * @member {PIXI.Rectangle} - * @private */ this._frame = frame; /** - * The texture trim data. + * This is the trimmed area of original texture, before it was put in atlas * * @member {PIXI.Rectangle} */ @@ -106,20 +106,22 @@ this.height = 0; /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * This is the area of original texture, before it was put in atlas * * @member {PIXI.Rectangle} */ this.crop = crop || frame;//new math.Rectangle(0, 0, 1, 1); - /** - * Indicates whether the texture should be rotated by 90 degrees - * - * @private - * @member {boolean} - */ - this.rotate = !!rotate; + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } if (baseTexture.hasLoaded) { @@ -168,10 +170,7 @@ this.noFrame = false; - this.width = frame.width; - this.height = frame.height; - - if (!this.trim && !this.rotate && (frame.x + frame.width > this.baseTexture.width || frame.y + frame.height > this.baseTexture.height)) + if (frame.x + frame.width > this.baseTexture.width || frame.y + frame.height > this.baseTexture.height) { throw new Error('Texture Error: frame does not fit inside the base Texture dimensions ' + this); } @@ -179,15 +178,9 @@ //this.valid = frame && frame.width && frame.height && this.baseTexture.source && this.baseTexture.hasLoaded; this.valid = frame && frame.width && frame.height && this.baseTexture.hasLoaded; - if (this.trim) - { - this.width = this.trim.width; - this.height = this.trim.height; - this._frame.width = this.trim.width; - this._frame.height = this.trim.height; - } - else - { + if (!this.trim) { + this.width = frame.width; + this.height = frame.height; this.crop = frame; } @@ -196,6 +189,29 @@ this._updateUvs(); } } + }, + /** + * Indicates whether the texture is rotated inside the atlas + * set to 2 to compensate for texture packer rotation + * set to 6 to compensate for spine packer rotation + * can be used to rotate or mirror sprites + * See {@link PIXI.GroupD8} for explanation + * + * @member {number} + */ + rotate: { + get: function () + { + return this._rotate; + }, + set: function (rotate) + { + this._rotate = rotate; + if (this.valid) + { + this._updateUvs(); + } + } } }); @@ -294,7 +310,7 @@ this._uvs = new TextureUvs(); } - this._uvs.set(this.crop, this.baseTexture, this.rotate); + this._uvs.set(this._frame, this.baseTexture, this.rotate); }; /** diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index c5e4564..11bed5d 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -30,11 +30,13 @@ module.exports = TextureUvs; +var GroupD8 = require('../math/GroupD8'); + /** * Sets the texture Uvs based on the given frame information * @param frame {PIXI.Rectangle} * @param baseFrame {PIXI.Rectangle} - * @param rotate {boolean} Whether or not the frame is rotated + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} * @private */ TextureUvs.prototype.set = function (frame, baseFrame, rotate) @@ -44,17 +46,24 @@ if(rotate) { - this.x0 = (frame.x + frame.height) / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.height) / tw; - this.y1 = (frame.y + frame.width) / th; - - this.x2 = frame.x / tw; - this.y2 = (frame.y + frame.width) / th; - - this.x3 = frame.x / tw; - this.y3 = frame.y / th; + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); } else { diff --git a/src/loaders/spritesheetParser.js b/src/loaders/spritesheetParser.js index b318d6f..fbd1ea5 100644 --- a/src/loaders/spritesheetParser.js +++ b/src/loaders/spritesheetParser.js @@ -35,14 +35,15 @@ if (rect) { - var size = null; + var frame = null; var trim = null; + var crop = new core.Rectangle(0, 0, frames[i].sourceSize.w / resolution, frames[i].sourceSize.h / resolution); if (frames[i].rotated) { - size = new core.Rectangle(rect.x, rect.y, rect.h, rect.w); + frame = new core.Rectangle(rect.x / resolution, rect.y / resolution, rect.h / resolution, rect.w / resolution); } else { - size = new core.Rectangle(rect.x, rect.y, rect.w, rect.h); + frame = new core.Rectangle(rect.x / resolution, rect.y / resolution, rect.w / resolution, rect.h / resolution); } // Check to see if the sprite is trimmed @@ -51,25 +52,12 @@ trim = new core.Rectangle( frames[i].spriteSourceSize.x / resolution, frames[i].spriteSourceSize.y / resolution, - frames[i].sourceSize.w / resolution, - frames[i].sourceSize.h / resolution + frames[i].spriteSourceSize.w / resolution, + frames[i].spriteSourceSize.h / resolution ); } - // flip the width and height! - if (frames[i].rotated) - { - var temp = size.width; - size.width = size.height; - size.height = temp; - } - - size.x /= resolution; - size.y /= resolution; - size.width /= resolution; - size.height /= resolution; - - resource.textures[i] = new core.Texture(res.texture.baseTexture, size, size.clone(), trim, frames[i].rotated); + resource.textures[i] = new core.Texture(res.texture.baseTexture, frame, crop, trim, frames[i].rotated ? 2 : 0); // lets also add the frame to pixi's global cache for fromFrame and fromImage functions core.utils.TextureCache[i] = resource.textures[i];