diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 6c7049c..6a3612c 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -87,7 +87,7 @@ */ addChildAt(child, index) { - if (index < 0 || index >= this.children.length) + if (index < 0 || index > this.children.length) { throw new Error(`${child}addChildAt: The index ${index} supplied is out of bounds ${this.children.length}`); } diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 6c7049c..6a3612c 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -87,7 +87,7 @@ */ addChildAt(child, index) { - if (index < 0 || index >= this.children.length) + if (index < 0 || index > this.children.length) { throw new Error(`${child}addChildAt: The index ${index} supplied is out of bounds ${this.children.length}`); } diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index bab0a01..455d59c 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -141,6 +141,12 @@ */ this._updateID = 0; + + /** + * Extra field for extra plugins. May contain clamp settings and some matrices + * @type {Object} + */ + this.transform = null; } /** diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 6c7049c..6a3612c 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -87,7 +87,7 @@ */ addChildAt(child, index) { - if (index < 0 || index >= this.children.length) + if (index < 0 || index > this.children.length) { throw new Error(`${child}addChildAt: The index ${index} supplied is out of bounds ${this.children.length}`); } diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index bab0a01..455d59c 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -141,6 +141,12 @@ */ this._updateID = 0; + + /** + * Extra field for extra plugins. May contain clamp settings and some matrices + * @type {Object} + */ + this.transform = null; } /** diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js new file mode 100644 index 0000000..88648b6 --- /dev/null +++ b/src/extras/TextureTransform.js @@ -0,0 +1,115 @@ +import { default as Matrix } from '../core/math/Matrix'; + +const tempMat = new Matrix(); + +/** + * class controls uv transform and frame clamp for texture + */ +export default class TextureTransform { + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you tex ture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + * @memberof PIXI.TextureTransform + */ + get texture() + { + return this._texture; + } + + /** + * sets texture value + * @param {PIXI.Texture} value texture to be set + */ + set texture(value) + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + */ + update(forceUpdate) + { + const tex = this.texture; + + if (!tex || !tex.valid) + { + return; + } + + if (!forceUpdate + && this._lastTextureID === this.texture._updateID) + { + return; + } + + this._lastTextureID = this.texture._updateID; + + const uvs = this.texture._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + } +} diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 6c7049c..6a3612c 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -87,7 +87,7 @@ */ addChildAt(child, index) { - if (index < 0 || index >= this.children.length) + if (index < 0 || index > this.children.length) { throw new Error(`${child}addChildAt: The index ${index} supplied is out of bounds ${this.children.length}`); } diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index bab0a01..455d59c 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -141,6 +141,12 @@ */ this._updateID = 0; + + /** + * Extra field for extra plugins. May contain clamp settings and some matrices + * @type {Object} + */ + this.transform = null; } /** diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js new file mode 100644 index 0000000..88648b6 --- /dev/null +++ b/src/extras/TextureTransform.js @@ -0,0 +1,115 @@ +import { default as Matrix } from '../core/math/Matrix'; + +const tempMat = new Matrix(); + +/** + * class controls uv transform and frame clamp for texture + */ +export default class TextureTransform { + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you tex ture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + * @memberof PIXI.TextureTransform + */ + get texture() + { + return this._texture; + } + + /** + * sets texture value + * @param {PIXI.Texture} value texture to be set + */ + set texture(value) + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + */ + update(forceUpdate) + { + const tex = this.texture; + + if (!tex || !tex.valid) + { + return; + } + + if (!forceUpdate + && this._lastTextureID === this.texture._updateID) + { + return; + } + + this._lastTextureID = this.texture._updateID; + + const uvs = this.texture._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + } +} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 2965caa..af69ffa 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,9 +1,7 @@ import * as core from '../core'; -import Texture from '../core/textures/Texture'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import TilingShader from './webgl/TilingShader'; +import { default as TextureTransform } from './TextureTransform'; -const tempArray = new Float32Array(4); const tempPoint = new core.Point(); /** @@ -25,18 +23,11 @@ super(texture); /** - * The scaling of the image that is being tiled + * Tile transform * - * @member {PIXI.Point} + * @member {PIXI.TransformStatic} */ - this.tileScale = new core.Point(1, 1); - - /** - * The offset position of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0, 0); + this.tileTransform = new core.TransformStatic(); // /// private @@ -57,16 +48,84 @@ this._height = height; /** - * An internal WebGL UV cache. + * Canvas pattern * - * @member {PIXI.TextureUvs} + * @type {CanvasPattern} * @private */ - this._uvs = new core.TextureUvs(); - this._canvasPattern = null; - this._glDatas = []; + /** + * transform that is applied to UV to get the texture coords + * + * @member {PIXI.extras.TextureTransform} + */ + this.uvTransform = texture.transform || new TextureTransform(texture); + } + /** + * Changes frame clamping in corresponding textureTransform, shortcut + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + * @memberof PIXI.TilingSprite + */ + get clampMargin() + { + return this.uvTransform.clampMargin; + } + + /** + * setter for clampMargin + * + * @param {number} value assigned value + */ + set clampMargin(value) + { + this.uvTransform.clampMargin = value; + this.uvTransform.update(true); + } + + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.ObservablePoint} + * @memberof PIXI.DisplayObject# + */ + get tileScale() + { + return this.tileTransform.scale; + } + + /** + * Copies the point to the scale of the tiled image. + * + * @param {PIXI.Point|PIXI.ObservablePoint} value - The value to set to. + */ + set tileScale(value) + { + this.tileTransform.scale.copy(value); + } + + /** + * The offset of the image that is being tiled + * + * @member {PIXI.ObservablePoint} + * @memberof PIXI.TilingSprite# + */ + get tilePosition() + { + return this.tileTransform.position; + } + + /** + * Copies the point to the position of the tiled image. + * + * @param {PIXI.Point|PIXI.ObservablePoint} value - The value to set to. + */ + set tilePosition(value) + { + this.tileTransform.position.copy(value); } /** @@ -74,7 +133,10 @@ */ _onTextureUpdate() { - return; + if (this.uvTransform) + { + this.uvTransform.texture = this._texture; + } } /** @@ -88,84 +150,16 @@ // tweak our texture temporarily.. const texture = this._texture; - if (!texture || !texture._uvs) + if (!texture || !texture.valid) { return; } - // get rid of any thing that may be batching. - renderer.flush(); + this.tileTransform.updateLocalTransform(); + this.uvTransform.update(); - const gl = renderer.gl; - let glData = this._glDatas[renderer.CONTEXT_UID]; - - if (!glData) - { - glData = { - shader: new TilingShader(gl), - quad: new core.Quad(gl), - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space - // before transforming the sprite coords.. - const vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = (this._width) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = (this._width) * (1 - this.anchor.x); - vertices[5] = vertices[7] = this._height * (1 - this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - const textureUvs = texture._uvs; - const textureWidth = texture._frame.width; - const textureHeight = texture._frame.height; - const textureBaseWidth = texture.baseTexture.width; - const textureBaseHeight = texture.baseTexture.height; - - const uPixelSize = glData.shader.uniforms.uPixelSize; - - uPixelSize[0] = 1.0 / textureBaseWidth; - uPixelSize[1] = 1.0 / textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - const uFrame = glData.shader.uniforms.uFrame; - - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - const uTransform = glData.shader.uniforms.uTransform; - - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = (textureBaseWidth / this._width) * this.tileScale.x; - uTransform[3] = (textureBaseHeight / this._height) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - const color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode(this.blendMode); - glData.quad.draw(); + renderer.setObjectRenderer(renderer.plugins.tilingSprite); + renderer.plugins.tilingSprite.render(this); } /** @@ -247,6 +241,38 @@ } /** + * Gets the local bounds of the sprite object. + * + * @param {PIXI.Rectangle} rect - The output rectangle. + * @return {PIXI.Rectangle} The bounds. + */ + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if (this.children.length === 0) + { + this._bounds.minX = -this._width * this.anchor._x; + this._bounds.minY = -this._height * this.anchor._y; + this._bounds.maxX = this._width; + this._bounds.maxY = this._height; + + if (!rect) + { + if (!this._localBoundsRect) + { + this._localBoundsRect = new core.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + + return super.getLocalBounds.call(this, rect); + } + + /** * Returns the framing rectangle of the sprite as a Rectangle object * * @return {PIXI.Rectangle} the framing rectangle @@ -358,10 +384,7 @@ super.destroy(); this.tileScale = null; - this._tileScaleOffset = null; this.tilePosition = null; - - this._uvs = null; } /** @@ -376,7 +399,7 @@ */ static from(source, width, height) { - return new TilingSprite(Texture.from(source), width, height); + return new TilingSprite(core.Texture.from(source), width, height); } /** diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 6c7049c..6a3612c 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -87,7 +87,7 @@ */ addChildAt(child, index) { - if (index < 0 || index >= this.children.length) + if (index < 0 || index > this.children.length) { throw new Error(`${child}addChildAt: The index ${index} supplied is out of bounds ${this.children.length}`); } diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index bab0a01..455d59c 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -141,6 +141,12 @@ */ this._updateID = 0; + + /** + * Extra field for extra plugins. May contain clamp settings and some matrices + * @type {Object} + */ + this.transform = null; } /** diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js new file mode 100644 index 0000000..88648b6 --- /dev/null +++ b/src/extras/TextureTransform.js @@ -0,0 +1,115 @@ +import { default as Matrix } from '../core/math/Matrix'; + +const tempMat = new Matrix(); + +/** + * class controls uv transform and frame clamp for texture + */ +export default class TextureTransform { + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you tex ture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + * @memberof PIXI.TextureTransform + */ + get texture() + { + return this._texture; + } + + /** + * sets texture value + * @param {PIXI.Texture} value texture to be set + */ + set texture(value) + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + */ + update(forceUpdate) + { + const tex = this.texture; + + if (!tex || !tex.valid) + { + return; + } + + if (!forceUpdate + && this._lastTextureID === this.texture._updateID) + { + return; + } + + this._lastTextureID = this.texture._updateID; + + const uvs = this.texture._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + } +} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 2965caa..af69ffa 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,9 +1,7 @@ import * as core from '../core'; -import Texture from '../core/textures/Texture'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import TilingShader from './webgl/TilingShader'; +import { default as TextureTransform } from './TextureTransform'; -const tempArray = new Float32Array(4); const tempPoint = new core.Point(); /** @@ -25,18 +23,11 @@ super(texture); /** - * The scaling of the image that is being tiled + * Tile transform * - * @member {PIXI.Point} + * @member {PIXI.TransformStatic} */ - this.tileScale = new core.Point(1, 1); - - /** - * The offset position of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0, 0); + this.tileTransform = new core.TransformStatic(); // /// private @@ -57,16 +48,84 @@ this._height = height; /** - * An internal WebGL UV cache. + * Canvas pattern * - * @member {PIXI.TextureUvs} + * @type {CanvasPattern} * @private */ - this._uvs = new core.TextureUvs(); - this._canvasPattern = null; - this._glDatas = []; + /** + * transform that is applied to UV to get the texture coords + * + * @member {PIXI.extras.TextureTransform} + */ + this.uvTransform = texture.transform || new TextureTransform(texture); + } + /** + * Changes frame clamping in corresponding textureTransform, shortcut + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + * @memberof PIXI.TilingSprite + */ + get clampMargin() + { + return this.uvTransform.clampMargin; + } + + /** + * setter for clampMargin + * + * @param {number} value assigned value + */ + set clampMargin(value) + { + this.uvTransform.clampMargin = value; + this.uvTransform.update(true); + } + + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.ObservablePoint} + * @memberof PIXI.DisplayObject# + */ + get tileScale() + { + return this.tileTransform.scale; + } + + /** + * Copies the point to the scale of the tiled image. + * + * @param {PIXI.Point|PIXI.ObservablePoint} value - The value to set to. + */ + set tileScale(value) + { + this.tileTransform.scale.copy(value); + } + + /** + * The offset of the image that is being tiled + * + * @member {PIXI.ObservablePoint} + * @memberof PIXI.TilingSprite# + */ + get tilePosition() + { + return this.tileTransform.position; + } + + /** + * Copies the point to the position of the tiled image. + * + * @param {PIXI.Point|PIXI.ObservablePoint} value - The value to set to. + */ + set tilePosition(value) + { + this.tileTransform.position.copy(value); } /** @@ -74,7 +133,10 @@ */ _onTextureUpdate() { - return; + if (this.uvTransform) + { + this.uvTransform.texture = this._texture; + } } /** @@ -88,84 +150,16 @@ // tweak our texture temporarily.. const texture = this._texture; - if (!texture || !texture._uvs) + if (!texture || !texture.valid) { return; } - // get rid of any thing that may be batching. - renderer.flush(); + this.tileTransform.updateLocalTransform(); + this.uvTransform.update(); - const gl = renderer.gl; - let glData = this._glDatas[renderer.CONTEXT_UID]; - - if (!glData) - { - glData = { - shader: new TilingShader(gl), - quad: new core.Quad(gl), - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space - // before transforming the sprite coords.. - const vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = (this._width) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = (this._width) * (1 - this.anchor.x); - vertices[5] = vertices[7] = this._height * (1 - this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - const textureUvs = texture._uvs; - const textureWidth = texture._frame.width; - const textureHeight = texture._frame.height; - const textureBaseWidth = texture.baseTexture.width; - const textureBaseHeight = texture.baseTexture.height; - - const uPixelSize = glData.shader.uniforms.uPixelSize; - - uPixelSize[0] = 1.0 / textureBaseWidth; - uPixelSize[1] = 1.0 / textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - const uFrame = glData.shader.uniforms.uFrame; - - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - const uTransform = glData.shader.uniforms.uTransform; - - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = (textureBaseWidth / this._width) * this.tileScale.x; - uTransform[3] = (textureBaseHeight / this._height) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - const color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode(this.blendMode); - glData.quad.draw(); + renderer.setObjectRenderer(renderer.plugins.tilingSprite); + renderer.plugins.tilingSprite.render(this); } /** @@ -247,6 +241,38 @@ } /** + * Gets the local bounds of the sprite object. + * + * @param {PIXI.Rectangle} rect - The output rectangle. + * @return {PIXI.Rectangle} The bounds. + */ + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if (this.children.length === 0) + { + this._bounds.minX = -this._width * this.anchor._x; + this._bounds.minY = -this._height * this.anchor._y; + this._bounds.maxX = this._width; + this._bounds.maxY = this._height; + + if (!rect) + { + if (!this._localBoundsRect) + { + this._localBoundsRect = new core.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + + return super.getLocalBounds.call(this, rect); + } + + /** * Returns the framing rectangle of the sprite as a Rectangle object * * @return {PIXI.Rectangle} the framing rectangle @@ -358,10 +384,7 @@ super.destroy(); this.tileScale = null; - this._tileScaleOffset = null; this.tilePosition = null; - - this._uvs = null; } /** @@ -376,7 +399,7 @@ */ static from(source, width, height) { - return new TilingSprite(Texture.from(source), width, height); + return new TilingSprite(core.Texture.from(source), width, height); } /** diff --git a/src/extras/index.js b/src/extras/index.js index 73bed41..7fe38b3 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,8 +1,10 @@ /** * @namespace PIXI.extras */ +export { default as TextureTransform } from './TextureTransform'; export { default as MovieClip } from './MovieClip'; export { default as TilingSprite } from './TilingSprite'; +export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; export { default as cacheAsBitmap } from './cacheAsBitmap'; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 6c7049c..6a3612c 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -87,7 +87,7 @@ */ addChildAt(child, index) { - if (index < 0 || index >= this.children.length) + if (index < 0 || index > this.children.length) { throw new Error(`${child}addChildAt: The index ${index} supplied is out of bounds ${this.children.length}`); } diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index bab0a01..455d59c 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -141,6 +141,12 @@ */ this._updateID = 0; + + /** + * Extra field for extra plugins. May contain clamp settings and some matrices + * @type {Object} + */ + this.transform = null; } /** diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js new file mode 100644 index 0000000..88648b6 --- /dev/null +++ b/src/extras/TextureTransform.js @@ -0,0 +1,115 @@ +import { default as Matrix } from '../core/math/Matrix'; + +const tempMat = new Matrix(); + +/** + * class controls uv transform and frame clamp for texture + */ +export default class TextureTransform { + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you tex ture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + * @memberof PIXI.TextureTransform + */ + get texture() + { + return this._texture; + } + + /** + * sets texture value + * @param {PIXI.Texture} value texture to be set + */ + set texture(value) + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + */ + update(forceUpdate) + { + const tex = this.texture; + + if (!tex || !tex.valid) + { + return; + } + + if (!forceUpdate + && this._lastTextureID === this.texture._updateID) + { + return; + } + + this._lastTextureID = this.texture._updateID; + + const uvs = this.texture._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + } +} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 2965caa..af69ffa 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,9 +1,7 @@ import * as core from '../core'; -import Texture from '../core/textures/Texture'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import TilingShader from './webgl/TilingShader'; +import { default as TextureTransform } from './TextureTransform'; -const tempArray = new Float32Array(4); const tempPoint = new core.Point(); /** @@ -25,18 +23,11 @@ super(texture); /** - * The scaling of the image that is being tiled + * Tile transform * - * @member {PIXI.Point} + * @member {PIXI.TransformStatic} */ - this.tileScale = new core.Point(1, 1); - - /** - * The offset position of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0, 0); + this.tileTransform = new core.TransformStatic(); // /// private @@ -57,16 +48,84 @@ this._height = height; /** - * An internal WebGL UV cache. + * Canvas pattern * - * @member {PIXI.TextureUvs} + * @type {CanvasPattern} * @private */ - this._uvs = new core.TextureUvs(); - this._canvasPattern = null; - this._glDatas = []; + /** + * transform that is applied to UV to get the texture coords + * + * @member {PIXI.extras.TextureTransform} + */ + this.uvTransform = texture.transform || new TextureTransform(texture); + } + /** + * Changes frame clamping in corresponding textureTransform, shortcut + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + * @memberof PIXI.TilingSprite + */ + get clampMargin() + { + return this.uvTransform.clampMargin; + } + + /** + * setter for clampMargin + * + * @param {number} value assigned value + */ + set clampMargin(value) + { + this.uvTransform.clampMargin = value; + this.uvTransform.update(true); + } + + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.ObservablePoint} + * @memberof PIXI.DisplayObject# + */ + get tileScale() + { + return this.tileTransform.scale; + } + + /** + * Copies the point to the scale of the tiled image. + * + * @param {PIXI.Point|PIXI.ObservablePoint} value - The value to set to. + */ + set tileScale(value) + { + this.tileTransform.scale.copy(value); + } + + /** + * The offset of the image that is being tiled + * + * @member {PIXI.ObservablePoint} + * @memberof PIXI.TilingSprite# + */ + get tilePosition() + { + return this.tileTransform.position; + } + + /** + * Copies the point to the position of the tiled image. + * + * @param {PIXI.Point|PIXI.ObservablePoint} value - The value to set to. + */ + set tilePosition(value) + { + this.tileTransform.position.copy(value); } /** @@ -74,7 +133,10 @@ */ _onTextureUpdate() { - return; + if (this.uvTransform) + { + this.uvTransform.texture = this._texture; + } } /** @@ -88,84 +150,16 @@ // tweak our texture temporarily.. const texture = this._texture; - if (!texture || !texture._uvs) + if (!texture || !texture.valid) { return; } - // get rid of any thing that may be batching. - renderer.flush(); + this.tileTransform.updateLocalTransform(); + this.uvTransform.update(); - const gl = renderer.gl; - let glData = this._glDatas[renderer.CONTEXT_UID]; - - if (!glData) - { - glData = { - shader: new TilingShader(gl), - quad: new core.Quad(gl), - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space - // before transforming the sprite coords.. - const vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = (this._width) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = (this._width) * (1 - this.anchor.x); - vertices[5] = vertices[7] = this._height * (1 - this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - const textureUvs = texture._uvs; - const textureWidth = texture._frame.width; - const textureHeight = texture._frame.height; - const textureBaseWidth = texture.baseTexture.width; - const textureBaseHeight = texture.baseTexture.height; - - const uPixelSize = glData.shader.uniforms.uPixelSize; - - uPixelSize[0] = 1.0 / textureBaseWidth; - uPixelSize[1] = 1.0 / textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - const uFrame = glData.shader.uniforms.uFrame; - - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - const uTransform = glData.shader.uniforms.uTransform; - - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = (textureBaseWidth / this._width) * this.tileScale.x; - uTransform[3] = (textureBaseHeight / this._height) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - const color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode(this.blendMode); - glData.quad.draw(); + renderer.setObjectRenderer(renderer.plugins.tilingSprite); + renderer.plugins.tilingSprite.render(this); } /** @@ -247,6 +241,38 @@ } /** + * Gets the local bounds of the sprite object. + * + * @param {PIXI.Rectangle} rect - The output rectangle. + * @return {PIXI.Rectangle} The bounds. + */ + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if (this.children.length === 0) + { + this._bounds.minX = -this._width * this.anchor._x; + this._bounds.minY = -this._height * this.anchor._y; + this._bounds.maxX = this._width; + this._bounds.maxY = this._height; + + if (!rect) + { + if (!this._localBoundsRect) + { + this._localBoundsRect = new core.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + + return super.getLocalBounds.call(this, rect); + } + + /** * Returns the framing rectangle of the sprite as a Rectangle object * * @return {PIXI.Rectangle} the framing rectangle @@ -358,10 +384,7 @@ super.destroy(); this.tileScale = null; - this._tileScaleOffset = null; this.tilePosition = null; - - this._uvs = null; } /** @@ -376,7 +399,7 @@ */ static from(source, width, height) { - return new TilingSprite(Texture.from(source), width, height); + return new TilingSprite(core.Texture.from(source), width, height); } /** diff --git a/src/extras/index.js b/src/extras/index.js index 73bed41..7fe38b3 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,8 +1,10 @@ /** * @namespace PIXI.extras */ +export { default as TextureTransform } from './TextureTransform'; export { default as MovieClip } from './MovieClip'; export { default as TilingSprite } from './TilingSprite'; +export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; export { default as cacheAsBitmap } from './cacheAsBitmap'; diff --git a/src/extras/webgl/TilingShader.js b/src/extras/webgl/TilingShader.js deleted file mode 100644 index 1e63a14..0000000 --- a/src/extras/webgl/TilingShader.js +++ /dev/null @@ -1,22 +0,0 @@ -import Shader from '../../core/Shader'; -const glslify = require('glslify'); // eslint-disable-line no-undef - -/** - * @class - * @extends PIXI.Shader - * @memberof PIXI.mesh - */ -export default class TilingShader extends Shader -{ - /** - * @param {WebGLRenderingContext} gl - The WebGL rendering context. - */ - constructor(gl) - { - super( - gl, - glslify('./tilingSprite.vert'), - glslify('./tilingSprite.frag') - ); - } -} diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 6c7049c..6a3612c 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -87,7 +87,7 @@ */ addChildAt(child, index) { - if (index < 0 || index >= this.children.length) + if (index < 0 || index > this.children.length) { throw new Error(`${child}addChildAt: The index ${index} supplied is out of bounds ${this.children.length}`); } diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index bab0a01..455d59c 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -141,6 +141,12 @@ */ this._updateID = 0; + + /** + * Extra field for extra plugins. May contain clamp settings and some matrices + * @type {Object} + */ + this.transform = null; } /** diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js new file mode 100644 index 0000000..88648b6 --- /dev/null +++ b/src/extras/TextureTransform.js @@ -0,0 +1,115 @@ +import { default as Matrix } from '../core/math/Matrix'; + +const tempMat = new Matrix(); + +/** + * class controls uv transform and frame clamp for texture + */ +export default class TextureTransform { + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you tex ture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + * @memberof PIXI.TextureTransform + */ + get texture() + { + return this._texture; + } + + /** + * sets texture value + * @param {PIXI.Texture} value texture to be set + */ + set texture(value) + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + */ + update(forceUpdate) + { + const tex = this.texture; + + if (!tex || !tex.valid) + { + return; + } + + if (!forceUpdate + && this._lastTextureID === this.texture._updateID) + { + return; + } + + this._lastTextureID = this.texture._updateID; + + const uvs = this.texture._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + } +} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 2965caa..af69ffa 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,9 +1,7 @@ import * as core from '../core'; -import Texture from '../core/textures/Texture'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import TilingShader from './webgl/TilingShader'; +import { default as TextureTransform } from './TextureTransform'; -const tempArray = new Float32Array(4); const tempPoint = new core.Point(); /** @@ -25,18 +23,11 @@ super(texture); /** - * The scaling of the image that is being tiled + * Tile transform * - * @member {PIXI.Point} + * @member {PIXI.TransformStatic} */ - this.tileScale = new core.Point(1, 1); - - /** - * The offset position of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0, 0); + this.tileTransform = new core.TransformStatic(); // /// private @@ -57,16 +48,84 @@ this._height = height; /** - * An internal WebGL UV cache. + * Canvas pattern * - * @member {PIXI.TextureUvs} + * @type {CanvasPattern} * @private */ - this._uvs = new core.TextureUvs(); - this._canvasPattern = null; - this._glDatas = []; + /** + * transform that is applied to UV to get the texture coords + * + * @member {PIXI.extras.TextureTransform} + */ + this.uvTransform = texture.transform || new TextureTransform(texture); + } + /** + * Changes frame clamping in corresponding textureTransform, shortcut + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + * @memberof PIXI.TilingSprite + */ + get clampMargin() + { + return this.uvTransform.clampMargin; + } + + /** + * setter for clampMargin + * + * @param {number} value assigned value + */ + set clampMargin(value) + { + this.uvTransform.clampMargin = value; + this.uvTransform.update(true); + } + + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.ObservablePoint} + * @memberof PIXI.DisplayObject# + */ + get tileScale() + { + return this.tileTransform.scale; + } + + /** + * Copies the point to the scale of the tiled image. + * + * @param {PIXI.Point|PIXI.ObservablePoint} value - The value to set to. + */ + set tileScale(value) + { + this.tileTransform.scale.copy(value); + } + + /** + * The offset of the image that is being tiled + * + * @member {PIXI.ObservablePoint} + * @memberof PIXI.TilingSprite# + */ + get tilePosition() + { + return this.tileTransform.position; + } + + /** + * Copies the point to the position of the tiled image. + * + * @param {PIXI.Point|PIXI.ObservablePoint} value - The value to set to. + */ + set tilePosition(value) + { + this.tileTransform.position.copy(value); } /** @@ -74,7 +133,10 @@ */ _onTextureUpdate() { - return; + if (this.uvTransform) + { + this.uvTransform.texture = this._texture; + } } /** @@ -88,84 +150,16 @@ // tweak our texture temporarily.. const texture = this._texture; - if (!texture || !texture._uvs) + if (!texture || !texture.valid) { return; } - // get rid of any thing that may be batching. - renderer.flush(); + this.tileTransform.updateLocalTransform(); + this.uvTransform.update(); - const gl = renderer.gl; - let glData = this._glDatas[renderer.CONTEXT_UID]; - - if (!glData) - { - glData = { - shader: new TilingShader(gl), - quad: new core.Quad(gl), - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space - // before transforming the sprite coords.. - const vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = (this._width) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = (this._width) * (1 - this.anchor.x); - vertices[5] = vertices[7] = this._height * (1 - this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - const textureUvs = texture._uvs; - const textureWidth = texture._frame.width; - const textureHeight = texture._frame.height; - const textureBaseWidth = texture.baseTexture.width; - const textureBaseHeight = texture.baseTexture.height; - - const uPixelSize = glData.shader.uniforms.uPixelSize; - - uPixelSize[0] = 1.0 / textureBaseWidth; - uPixelSize[1] = 1.0 / textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - const uFrame = glData.shader.uniforms.uFrame; - - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - const uTransform = glData.shader.uniforms.uTransform; - - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = (textureBaseWidth / this._width) * this.tileScale.x; - uTransform[3] = (textureBaseHeight / this._height) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - const color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode(this.blendMode); - glData.quad.draw(); + renderer.setObjectRenderer(renderer.plugins.tilingSprite); + renderer.plugins.tilingSprite.render(this); } /** @@ -247,6 +241,38 @@ } /** + * Gets the local bounds of the sprite object. + * + * @param {PIXI.Rectangle} rect - The output rectangle. + * @return {PIXI.Rectangle} The bounds. + */ + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if (this.children.length === 0) + { + this._bounds.minX = -this._width * this.anchor._x; + this._bounds.minY = -this._height * this.anchor._y; + this._bounds.maxX = this._width; + this._bounds.maxY = this._height; + + if (!rect) + { + if (!this._localBoundsRect) + { + this._localBoundsRect = new core.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + + return super.getLocalBounds.call(this, rect); + } + + /** * Returns the framing rectangle of the sprite as a Rectangle object * * @return {PIXI.Rectangle} the framing rectangle @@ -358,10 +384,7 @@ super.destroy(); this.tileScale = null; - this._tileScaleOffset = null; this.tilePosition = null; - - this._uvs = null; } /** @@ -376,7 +399,7 @@ */ static from(source, width, height) { - return new TilingSprite(Texture.from(source), width, height); + return new TilingSprite(core.Texture.from(source), width, height); } /** diff --git a/src/extras/index.js b/src/extras/index.js index 73bed41..7fe38b3 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,8 +1,10 @@ /** * @namespace PIXI.extras */ +export { default as TextureTransform } from './TextureTransform'; export { default as MovieClip } from './MovieClip'; export { default as TilingSprite } from './TilingSprite'; +export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; export { default as cacheAsBitmap } from './cacheAsBitmap'; diff --git a/src/extras/webgl/TilingShader.js b/src/extras/webgl/TilingShader.js deleted file mode 100644 index 1e63a14..0000000 --- a/src/extras/webgl/TilingShader.js +++ /dev/null @@ -1,22 +0,0 @@ -import Shader from '../../core/Shader'; -const glslify = require('glslify'); // eslint-disable-line no-undef - -/** - * @class - * @extends PIXI.Shader - * @memberof PIXI.mesh - */ -export default class TilingShader extends Shader -{ - /** - * @param {WebGLRenderingContext} gl - The WebGL rendering context. - */ - constructor(gl) - { - super( - gl, - glslify('./tilingSprite.vert'), - glslify('./tilingSprite.frag') - ); - } -} diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js new file mode 100644 index 0000000..b74d861 --- /dev/null +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -0,0 +1,146 @@ +import * as core from '../../core'; +import { WRAP_MODES } from '../../core/const'; + +const glslify = require('glslify'); // eslint-disable-line no-undef + +const tempMat = new core.Matrix(); +const tempArray = new Float32Array(4); + +/** + * WebGL renderer plugin for tiling sprites + */ +export class TilingSpriteRenderer extends core.ObjectRenderer { + + /** + * constructor for renderer + * + * @param {WebGLRenderer} renderer The renderer this tiling awesomeness works for. + */ + constructor(renderer) + { + super(renderer); + + this.shader = null; + this.simpleShader = null; + this.quad = null; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + const gl = this.renderer.gl; + + this.shader = new core.Shader(gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite.frag')); + this.simpleShader = new core.Shader(gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite_simple.frag')); + + this.quad = new core.Quad(gl); + this.quad.initVao(this.shader); + } + + /** + * + * @param {PIXI.extras.TilingSprite} ts tilingSprite to be rendered + */ + render(ts) + { + const quad = this.quad; + let vertices = quad.vertices; + + vertices[0] = vertices[6] = (ts._width) * -ts.anchor.x; + vertices[1] = vertices[3] = ts._height * -ts.anchor.y; + + vertices[2] = vertices[4] = (ts._width) * (1.0 - ts.anchor.x); + vertices[5] = vertices[7] = ts._height * (1.0 - ts.anchor.y); + + vertices = quad.uvs; + + vertices[0] = vertices[6] = -ts.anchor.x; + vertices[1] = vertices[3] = -ts.anchor.y; + + vertices[2] = vertices[4] = 1.0 - ts.anchor.x; + vertices[5] = vertices[7] = 1.0 - ts.anchor.y; + + quad.upload(); + + const renderer = this.renderer; + const tex = ts._texture; + const baseTex = tex.baseTexture; + const lt = ts.tileTransform.localTransform; + const uv = ts.uvTransform; + let isSimple = baseTex.isPowerOfTwo + && tex.frame.width === baseTex.width && tex.frame.height === baseTex.height; + + // auto, force repeat wrapMode for big tiling textures + if (isSimple) + { + if (!baseTex._glTextures[renderer.CONTEXT_UID]) + { + if (baseTex.wrapMode === WRAP_MODES.CLAMP) + { + baseTex.wrapMode = WRAP_MODES.REPEAT; + } + } + else + { + isSimple = baseTex.wrapMode !== WRAP_MODES.CLAMP; + } + } + + const shader = isSimple ? this.simpleShader : this.shader; + + renderer.bindShader(shader); + + const w = tex.width; + const h = tex.height; + const W = ts._width; + const H = ts._height; + + tempMat.set(lt.a * w / W, + lt.b * w / H, + lt.c * h / W, + lt.d * h / H, + lt.tx / W, + lt.ty / H); + + // that part is the same as above: + // tempMat.identity(); + // tempMat.scale(tex.width, tex.height); + // tempMat.prepend(lt); + // tempMat.scale(1.0 / ts._width, 1.0 / ts._height); + + tempMat.invert(); + if (isSimple) + { + tempMat.append(uv.mapCoord); + } + else + { + shader.uniforms.uMapCoord = uv.mapCoord.toArray(true); + shader.uniforms.uClampFrame = uv.uClampFrame; + shader.uniforms.uClampOffset = uv.uClampOffset; + } + shader.uniforms.uTransform = tempMat.toArray(true); + + const color = tempArray; + + core.utils.hex2rgb(ts.tint, color); + color[3] = ts.worldAlpha; + shader.uniforms.uColor = color; + shader.uniforms.translationMatrix = ts.transform.worldTransform.toArray(true); + + renderer.bindTexture(tex); + renderer.setBlendMode(ts.blendMode); + + quad.draw(); + } +} + +core.WebGLRenderer.registerPlugin('tilingSprite', TilingSpriteRenderer); diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 6c7049c..6a3612c 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -87,7 +87,7 @@ */ addChildAt(child, index) { - if (index < 0 || index >= this.children.length) + if (index < 0 || index > this.children.length) { throw new Error(`${child}addChildAt: The index ${index} supplied is out of bounds ${this.children.length}`); } diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index bab0a01..455d59c 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -141,6 +141,12 @@ */ this._updateID = 0; + + /** + * Extra field for extra plugins. May contain clamp settings and some matrices + * @type {Object} + */ + this.transform = null; } /** diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js new file mode 100644 index 0000000..88648b6 --- /dev/null +++ b/src/extras/TextureTransform.js @@ -0,0 +1,115 @@ +import { default as Matrix } from '../core/math/Matrix'; + +const tempMat = new Matrix(); + +/** + * class controls uv transform and frame clamp for texture + */ +export default class TextureTransform { + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you tex ture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + * @memberof PIXI.TextureTransform + */ + get texture() + { + return this._texture; + } + + /** + * sets texture value + * @param {PIXI.Texture} value texture to be set + */ + set texture(value) + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + */ + update(forceUpdate) + { + const tex = this.texture; + + if (!tex || !tex.valid) + { + return; + } + + if (!forceUpdate + && this._lastTextureID === this.texture._updateID) + { + return; + } + + this._lastTextureID = this.texture._updateID; + + const uvs = this.texture._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + } +} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 2965caa..af69ffa 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,9 +1,7 @@ import * as core from '../core'; -import Texture from '../core/textures/Texture'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import TilingShader from './webgl/TilingShader'; +import { default as TextureTransform } from './TextureTransform'; -const tempArray = new Float32Array(4); const tempPoint = new core.Point(); /** @@ -25,18 +23,11 @@ super(texture); /** - * The scaling of the image that is being tiled + * Tile transform * - * @member {PIXI.Point} + * @member {PIXI.TransformStatic} */ - this.tileScale = new core.Point(1, 1); - - /** - * The offset position of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0, 0); + this.tileTransform = new core.TransformStatic(); // /// private @@ -57,16 +48,84 @@ this._height = height; /** - * An internal WebGL UV cache. + * Canvas pattern * - * @member {PIXI.TextureUvs} + * @type {CanvasPattern} * @private */ - this._uvs = new core.TextureUvs(); - this._canvasPattern = null; - this._glDatas = []; + /** + * transform that is applied to UV to get the texture coords + * + * @member {PIXI.extras.TextureTransform} + */ + this.uvTransform = texture.transform || new TextureTransform(texture); + } + /** + * Changes frame clamping in corresponding textureTransform, shortcut + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + * @memberof PIXI.TilingSprite + */ + get clampMargin() + { + return this.uvTransform.clampMargin; + } + + /** + * setter for clampMargin + * + * @param {number} value assigned value + */ + set clampMargin(value) + { + this.uvTransform.clampMargin = value; + this.uvTransform.update(true); + } + + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.ObservablePoint} + * @memberof PIXI.DisplayObject# + */ + get tileScale() + { + return this.tileTransform.scale; + } + + /** + * Copies the point to the scale of the tiled image. + * + * @param {PIXI.Point|PIXI.ObservablePoint} value - The value to set to. + */ + set tileScale(value) + { + this.tileTransform.scale.copy(value); + } + + /** + * The offset of the image that is being tiled + * + * @member {PIXI.ObservablePoint} + * @memberof PIXI.TilingSprite# + */ + get tilePosition() + { + return this.tileTransform.position; + } + + /** + * Copies the point to the position of the tiled image. + * + * @param {PIXI.Point|PIXI.ObservablePoint} value - The value to set to. + */ + set tilePosition(value) + { + this.tileTransform.position.copy(value); } /** @@ -74,7 +133,10 @@ */ _onTextureUpdate() { - return; + if (this.uvTransform) + { + this.uvTransform.texture = this._texture; + } } /** @@ -88,84 +150,16 @@ // tweak our texture temporarily.. const texture = this._texture; - if (!texture || !texture._uvs) + if (!texture || !texture.valid) { return; } - // get rid of any thing that may be batching. - renderer.flush(); + this.tileTransform.updateLocalTransform(); + this.uvTransform.update(); - const gl = renderer.gl; - let glData = this._glDatas[renderer.CONTEXT_UID]; - - if (!glData) - { - glData = { - shader: new TilingShader(gl), - quad: new core.Quad(gl), - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space - // before transforming the sprite coords.. - const vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = (this._width) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = (this._width) * (1 - this.anchor.x); - vertices[5] = vertices[7] = this._height * (1 - this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - const textureUvs = texture._uvs; - const textureWidth = texture._frame.width; - const textureHeight = texture._frame.height; - const textureBaseWidth = texture.baseTexture.width; - const textureBaseHeight = texture.baseTexture.height; - - const uPixelSize = glData.shader.uniforms.uPixelSize; - - uPixelSize[0] = 1.0 / textureBaseWidth; - uPixelSize[1] = 1.0 / textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - const uFrame = glData.shader.uniforms.uFrame; - - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - const uTransform = glData.shader.uniforms.uTransform; - - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = (textureBaseWidth / this._width) * this.tileScale.x; - uTransform[3] = (textureBaseHeight / this._height) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - const color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode(this.blendMode); - glData.quad.draw(); + renderer.setObjectRenderer(renderer.plugins.tilingSprite); + renderer.plugins.tilingSprite.render(this); } /** @@ -247,6 +241,38 @@ } /** + * Gets the local bounds of the sprite object. + * + * @param {PIXI.Rectangle} rect - The output rectangle. + * @return {PIXI.Rectangle} The bounds. + */ + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if (this.children.length === 0) + { + this._bounds.minX = -this._width * this.anchor._x; + this._bounds.minY = -this._height * this.anchor._y; + this._bounds.maxX = this._width; + this._bounds.maxY = this._height; + + if (!rect) + { + if (!this._localBoundsRect) + { + this._localBoundsRect = new core.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + + return super.getLocalBounds.call(this, rect); + } + + /** * Returns the framing rectangle of the sprite as a Rectangle object * * @return {PIXI.Rectangle} the framing rectangle @@ -358,10 +384,7 @@ super.destroy(); this.tileScale = null; - this._tileScaleOffset = null; this.tilePosition = null; - - this._uvs = null; } /** @@ -376,7 +399,7 @@ */ static from(source, width, height) { - return new TilingSprite(Texture.from(source), width, height); + return new TilingSprite(core.Texture.from(source), width, height); } /** diff --git a/src/extras/index.js b/src/extras/index.js index 73bed41..7fe38b3 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,8 +1,10 @@ /** * @namespace PIXI.extras */ +export { default as TextureTransform } from './TextureTransform'; export { default as MovieClip } from './MovieClip'; export { default as TilingSprite } from './TilingSprite'; +export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; export { default as cacheAsBitmap } from './cacheAsBitmap'; diff --git a/src/extras/webgl/TilingShader.js b/src/extras/webgl/TilingShader.js deleted file mode 100644 index 1e63a14..0000000 --- a/src/extras/webgl/TilingShader.js +++ /dev/null @@ -1,22 +0,0 @@ -import Shader from '../../core/Shader'; -const glslify = require('glslify'); // eslint-disable-line no-undef - -/** - * @class - * @extends PIXI.Shader - * @memberof PIXI.mesh - */ -export default class TilingShader extends Shader -{ - /** - * @param {WebGLRenderingContext} gl - The WebGL rendering context. - */ - constructor(gl) - { - super( - gl, - glslify('./tilingSprite.vert'), - glslify('./tilingSprite.frag') - ); - } -} diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js new file mode 100644 index 0000000..b74d861 --- /dev/null +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -0,0 +1,146 @@ +import * as core from '../../core'; +import { WRAP_MODES } from '../../core/const'; + +const glslify = require('glslify'); // eslint-disable-line no-undef + +const tempMat = new core.Matrix(); +const tempArray = new Float32Array(4); + +/** + * WebGL renderer plugin for tiling sprites + */ +export class TilingSpriteRenderer extends core.ObjectRenderer { + + /** + * constructor for renderer + * + * @param {WebGLRenderer} renderer The renderer this tiling awesomeness works for. + */ + constructor(renderer) + { + super(renderer); + + this.shader = null; + this.simpleShader = null; + this.quad = null; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + const gl = this.renderer.gl; + + this.shader = new core.Shader(gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite.frag')); + this.simpleShader = new core.Shader(gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite_simple.frag')); + + this.quad = new core.Quad(gl); + this.quad.initVao(this.shader); + } + + /** + * + * @param {PIXI.extras.TilingSprite} ts tilingSprite to be rendered + */ + render(ts) + { + const quad = this.quad; + let vertices = quad.vertices; + + vertices[0] = vertices[6] = (ts._width) * -ts.anchor.x; + vertices[1] = vertices[3] = ts._height * -ts.anchor.y; + + vertices[2] = vertices[4] = (ts._width) * (1.0 - ts.anchor.x); + vertices[5] = vertices[7] = ts._height * (1.0 - ts.anchor.y); + + vertices = quad.uvs; + + vertices[0] = vertices[6] = -ts.anchor.x; + vertices[1] = vertices[3] = -ts.anchor.y; + + vertices[2] = vertices[4] = 1.0 - ts.anchor.x; + vertices[5] = vertices[7] = 1.0 - ts.anchor.y; + + quad.upload(); + + const renderer = this.renderer; + const tex = ts._texture; + const baseTex = tex.baseTexture; + const lt = ts.tileTransform.localTransform; + const uv = ts.uvTransform; + let isSimple = baseTex.isPowerOfTwo + && tex.frame.width === baseTex.width && tex.frame.height === baseTex.height; + + // auto, force repeat wrapMode for big tiling textures + if (isSimple) + { + if (!baseTex._glTextures[renderer.CONTEXT_UID]) + { + if (baseTex.wrapMode === WRAP_MODES.CLAMP) + { + baseTex.wrapMode = WRAP_MODES.REPEAT; + } + } + else + { + isSimple = baseTex.wrapMode !== WRAP_MODES.CLAMP; + } + } + + const shader = isSimple ? this.simpleShader : this.shader; + + renderer.bindShader(shader); + + const w = tex.width; + const h = tex.height; + const W = ts._width; + const H = ts._height; + + tempMat.set(lt.a * w / W, + lt.b * w / H, + lt.c * h / W, + lt.d * h / H, + lt.tx / W, + lt.ty / H); + + // that part is the same as above: + // tempMat.identity(); + // tempMat.scale(tex.width, tex.height); + // tempMat.prepend(lt); + // tempMat.scale(1.0 / ts._width, 1.0 / ts._height); + + tempMat.invert(); + if (isSimple) + { + tempMat.append(uv.mapCoord); + } + else + { + shader.uniforms.uMapCoord = uv.mapCoord.toArray(true); + shader.uniforms.uClampFrame = uv.uClampFrame; + shader.uniforms.uClampOffset = uv.uClampOffset; + } + shader.uniforms.uTransform = tempMat.toArray(true); + + const color = tempArray; + + core.utils.hex2rgb(ts.tint, color); + color[3] = ts.worldAlpha; + shader.uniforms.uColor = color; + shader.uniforms.translationMatrix = ts.transform.worldTransform.toArray(true); + + renderer.bindTexture(tex); + renderer.setBlendMode(ts.blendMode); + + quad.draw(); + } +} + +core.WebGLRenderer.registerPlugin('tilingSprite', TilingSpriteRenderer); diff --git a/src/extras/webgl/tilingSprite.frag b/src/extras/webgl/tilingSprite.frag index 3996251..3caa383 100644 --- a/src/extras/webgl/tilingSprite.frag +++ b/src/extras/webgl/tilingSprite.frag @@ -2,14 +2,15 @@ uniform sampler2D uSampler; uniform vec4 uColor; -uniform vec4 uFrame; -uniform vec2 uPixelSize; +uniform mat3 uMapCoord; +uniform vec4 uClampFrame; +uniform vec2 uClampOffset; void main(void) { - vec2 coord = mod(vTextureCoord, uFrame.zw); - coord = clamp(coord, uPixelSize, uFrame.zw - uPixelSize); - coord += uFrame.xy; + vec2 coord = mod(vTextureCoord - uClampOffset, vec2(1.0, 1.0)) + uClampOffset; + coord = (uMapCoord * vec3(coord, 1.0)).xy; + coord = clamp(coord, uClampFrame.xy, uClampFrame.zw); vec4 sample = texture2D(uSampler, coord); vec4 color = vec4(uColor.rgb * uColor.a, uColor.a); diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 6c7049c..6a3612c 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -87,7 +87,7 @@ */ addChildAt(child, index) { - if (index < 0 || index >= this.children.length) + if (index < 0 || index > this.children.length) { throw new Error(`${child}addChildAt: The index ${index} supplied is out of bounds ${this.children.length}`); } diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index bab0a01..455d59c 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -141,6 +141,12 @@ */ this._updateID = 0; + + /** + * Extra field for extra plugins. May contain clamp settings and some matrices + * @type {Object} + */ + this.transform = null; } /** diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js new file mode 100644 index 0000000..88648b6 --- /dev/null +++ b/src/extras/TextureTransform.js @@ -0,0 +1,115 @@ +import { default as Matrix } from '../core/math/Matrix'; + +const tempMat = new Matrix(); + +/** + * class controls uv transform and frame clamp for texture + */ +export default class TextureTransform { + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you tex ture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + * @memberof PIXI.TextureTransform + */ + get texture() + { + return this._texture; + } + + /** + * sets texture value + * @param {PIXI.Texture} value texture to be set + */ + set texture(value) + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + */ + update(forceUpdate) + { + const tex = this.texture; + + if (!tex || !tex.valid) + { + return; + } + + if (!forceUpdate + && this._lastTextureID === this.texture._updateID) + { + return; + } + + this._lastTextureID = this.texture._updateID; + + const uvs = this.texture._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + } +} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 2965caa..af69ffa 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,9 +1,7 @@ import * as core from '../core'; -import Texture from '../core/textures/Texture'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import TilingShader from './webgl/TilingShader'; +import { default as TextureTransform } from './TextureTransform'; -const tempArray = new Float32Array(4); const tempPoint = new core.Point(); /** @@ -25,18 +23,11 @@ super(texture); /** - * The scaling of the image that is being tiled + * Tile transform * - * @member {PIXI.Point} + * @member {PIXI.TransformStatic} */ - this.tileScale = new core.Point(1, 1); - - /** - * The offset position of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0, 0); + this.tileTransform = new core.TransformStatic(); // /// private @@ -57,16 +48,84 @@ this._height = height; /** - * An internal WebGL UV cache. + * Canvas pattern * - * @member {PIXI.TextureUvs} + * @type {CanvasPattern} * @private */ - this._uvs = new core.TextureUvs(); - this._canvasPattern = null; - this._glDatas = []; + /** + * transform that is applied to UV to get the texture coords + * + * @member {PIXI.extras.TextureTransform} + */ + this.uvTransform = texture.transform || new TextureTransform(texture); + } + /** + * Changes frame clamping in corresponding textureTransform, shortcut + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + * @memberof PIXI.TilingSprite + */ + get clampMargin() + { + return this.uvTransform.clampMargin; + } + + /** + * setter for clampMargin + * + * @param {number} value assigned value + */ + set clampMargin(value) + { + this.uvTransform.clampMargin = value; + this.uvTransform.update(true); + } + + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.ObservablePoint} + * @memberof PIXI.DisplayObject# + */ + get tileScale() + { + return this.tileTransform.scale; + } + + /** + * Copies the point to the scale of the tiled image. + * + * @param {PIXI.Point|PIXI.ObservablePoint} value - The value to set to. + */ + set tileScale(value) + { + this.tileTransform.scale.copy(value); + } + + /** + * The offset of the image that is being tiled + * + * @member {PIXI.ObservablePoint} + * @memberof PIXI.TilingSprite# + */ + get tilePosition() + { + return this.tileTransform.position; + } + + /** + * Copies the point to the position of the tiled image. + * + * @param {PIXI.Point|PIXI.ObservablePoint} value - The value to set to. + */ + set tilePosition(value) + { + this.tileTransform.position.copy(value); } /** @@ -74,7 +133,10 @@ */ _onTextureUpdate() { - return; + if (this.uvTransform) + { + this.uvTransform.texture = this._texture; + } } /** @@ -88,84 +150,16 @@ // tweak our texture temporarily.. const texture = this._texture; - if (!texture || !texture._uvs) + if (!texture || !texture.valid) { return; } - // get rid of any thing that may be batching. - renderer.flush(); + this.tileTransform.updateLocalTransform(); + this.uvTransform.update(); - const gl = renderer.gl; - let glData = this._glDatas[renderer.CONTEXT_UID]; - - if (!glData) - { - glData = { - shader: new TilingShader(gl), - quad: new core.Quad(gl), - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space - // before transforming the sprite coords.. - const vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = (this._width) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = (this._width) * (1 - this.anchor.x); - vertices[5] = vertices[7] = this._height * (1 - this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - const textureUvs = texture._uvs; - const textureWidth = texture._frame.width; - const textureHeight = texture._frame.height; - const textureBaseWidth = texture.baseTexture.width; - const textureBaseHeight = texture.baseTexture.height; - - const uPixelSize = glData.shader.uniforms.uPixelSize; - - uPixelSize[0] = 1.0 / textureBaseWidth; - uPixelSize[1] = 1.0 / textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - const uFrame = glData.shader.uniforms.uFrame; - - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - const uTransform = glData.shader.uniforms.uTransform; - - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = (textureBaseWidth / this._width) * this.tileScale.x; - uTransform[3] = (textureBaseHeight / this._height) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - const color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode(this.blendMode); - glData.quad.draw(); + renderer.setObjectRenderer(renderer.plugins.tilingSprite); + renderer.plugins.tilingSprite.render(this); } /** @@ -247,6 +241,38 @@ } /** + * Gets the local bounds of the sprite object. + * + * @param {PIXI.Rectangle} rect - The output rectangle. + * @return {PIXI.Rectangle} The bounds. + */ + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if (this.children.length === 0) + { + this._bounds.minX = -this._width * this.anchor._x; + this._bounds.minY = -this._height * this.anchor._y; + this._bounds.maxX = this._width; + this._bounds.maxY = this._height; + + if (!rect) + { + if (!this._localBoundsRect) + { + this._localBoundsRect = new core.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + + return super.getLocalBounds.call(this, rect); + } + + /** * Returns the framing rectangle of the sprite as a Rectangle object * * @return {PIXI.Rectangle} the framing rectangle @@ -358,10 +384,7 @@ super.destroy(); this.tileScale = null; - this._tileScaleOffset = null; this.tilePosition = null; - - this._uvs = null; } /** @@ -376,7 +399,7 @@ */ static from(source, width, height) { - return new TilingSprite(Texture.from(source), width, height); + return new TilingSprite(core.Texture.from(source), width, height); } /** diff --git a/src/extras/index.js b/src/extras/index.js index 73bed41..7fe38b3 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,8 +1,10 @@ /** * @namespace PIXI.extras */ +export { default as TextureTransform } from './TextureTransform'; export { default as MovieClip } from './MovieClip'; export { default as TilingSprite } from './TilingSprite'; +export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; export { default as cacheAsBitmap } from './cacheAsBitmap'; diff --git a/src/extras/webgl/TilingShader.js b/src/extras/webgl/TilingShader.js deleted file mode 100644 index 1e63a14..0000000 --- a/src/extras/webgl/TilingShader.js +++ /dev/null @@ -1,22 +0,0 @@ -import Shader from '../../core/Shader'; -const glslify = require('glslify'); // eslint-disable-line no-undef - -/** - * @class - * @extends PIXI.Shader - * @memberof PIXI.mesh - */ -export default class TilingShader extends Shader -{ - /** - * @param {WebGLRenderingContext} gl - The WebGL rendering context. - */ - constructor(gl) - { - super( - gl, - glslify('./tilingSprite.vert'), - glslify('./tilingSprite.frag') - ); - } -} diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js new file mode 100644 index 0000000..b74d861 --- /dev/null +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -0,0 +1,146 @@ +import * as core from '../../core'; +import { WRAP_MODES } from '../../core/const'; + +const glslify = require('glslify'); // eslint-disable-line no-undef + +const tempMat = new core.Matrix(); +const tempArray = new Float32Array(4); + +/** + * WebGL renderer plugin for tiling sprites + */ +export class TilingSpriteRenderer extends core.ObjectRenderer { + + /** + * constructor for renderer + * + * @param {WebGLRenderer} renderer The renderer this tiling awesomeness works for. + */ + constructor(renderer) + { + super(renderer); + + this.shader = null; + this.simpleShader = null; + this.quad = null; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + const gl = this.renderer.gl; + + this.shader = new core.Shader(gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite.frag')); + this.simpleShader = new core.Shader(gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite_simple.frag')); + + this.quad = new core.Quad(gl); + this.quad.initVao(this.shader); + } + + /** + * + * @param {PIXI.extras.TilingSprite} ts tilingSprite to be rendered + */ + render(ts) + { + const quad = this.quad; + let vertices = quad.vertices; + + vertices[0] = vertices[6] = (ts._width) * -ts.anchor.x; + vertices[1] = vertices[3] = ts._height * -ts.anchor.y; + + vertices[2] = vertices[4] = (ts._width) * (1.0 - ts.anchor.x); + vertices[5] = vertices[7] = ts._height * (1.0 - ts.anchor.y); + + vertices = quad.uvs; + + vertices[0] = vertices[6] = -ts.anchor.x; + vertices[1] = vertices[3] = -ts.anchor.y; + + vertices[2] = vertices[4] = 1.0 - ts.anchor.x; + vertices[5] = vertices[7] = 1.0 - ts.anchor.y; + + quad.upload(); + + const renderer = this.renderer; + const tex = ts._texture; + const baseTex = tex.baseTexture; + const lt = ts.tileTransform.localTransform; + const uv = ts.uvTransform; + let isSimple = baseTex.isPowerOfTwo + && tex.frame.width === baseTex.width && tex.frame.height === baseTex.height; + + // auto, force repeat wrapMode for big tiling textures + if (isSimple) + { + if (!baseTex._glTextures[renderer.CONTEXT_UID]) + { + if (baseTex.wrapMode === WRAP_MODES.CLAMP) + { + baseTex.wrapMode = WRAP_MODES.REPEAT; + } + } + else + { + isSimple = baseTex.wrapMode !== WRAP_MODES.CLAMP; + } + } + + const shader = isSimple ? this.simpleShader : this.shader; + + renderer.bindShader(shader); + + const w = tex.width; + const h = tex.height; + const W = ts._width; + const H = ts._height; + + tempMat.set(lt.a * w / W, + lt.b * w / H, + lt.c * h / W, + lt.d * h / H, + lt.tx / W, + lt.ty / H); + + // that part is the same as above: + // tempMat.identity(); + // tempMat.scale(tex.width, tex.height); + // tempMat.prepend(lt); + // tempMat.scale(1.0 / ts._width, 1.0 / ts._height); + + tempMat.invert(); + if (isSimple) + { + tempMat.append(uv.mapCoord); + } + else + { + shader.uniforms.uMapCoord = uv.mapCoord.toArray(true); + shader.uniforms.uClampFrame = uv.uClampFrame; + shader.uniforms.uClampOffset = uv.uClampOffset; + } + shader.uniforms.uTransform = tempMat.toArray(true); + + const color = tempArray; + + core.utils.hex2rgb(ts.tint, color); + color[3] = ts.worldAlpha; + shader.uniforms.uColor = color; + shader.uniforms.translationMatrix = ts.transform.worldTransform.toArray(true); + + renderer.bindTexture(tex); + renderer.setBlendMode(ts.blendMode); + + quad.draw(); + } +} + +core.WebGLRenderer.registerPlugin('tilingSprite', TilingSpriteRenderer); diff --git a/src/extras/webgl/tilingSprite.frag b/src/extras/webgl/tilingSprite.frag index 3996251..3caa383 100644 --- a/src/extras/webgl/tilingSprite.frag +++ b/src/extras/webgl/tilingSprite.frag @@ -2,14 +2,15 @@ uniform sampler2D uSampler; uniform vec4 uColor; -uniform vec4 uFrame; -uniform vec2 uPixelSize; +uniform mat3 uMapCoord; +uniform vec4 uClampFrame; +uniform vec2 uClampOffset; void main(void) { - vec2 coord = mod(vTextureCoord, uFrame.zw); - coord = clamp(coord, uPixelSize, uFrame.zw - uPixelSize); - coord += uFrame.xy; + vec2 coord = mod(vTextureCoord - uClampOffset, vec2(1.0, 1.0)) + uClampOffset; + coord = (uMapCoord * vec3(coord, 1.0)).xy; + coord = clamp(coord, uClampFrame.xy, uClampFrame.zw); vec4 sample = texture2D(uSampler, coord); vec4 color = vec4(uColor.rgb * uColor.a, uColor.a); diff --git a/src/extras/webgl/tilingSprite.vert b/src/extras/webgl/tilingSprite.vert index bb78293..acc096c 100644 --- a/src/extras/webgl/tilingSprite.vert +++ b/src/extras/webgl/tilingSprite.vert @@ -3,9 +3,7 @@ uniform mat3 projectionMatrix; uniform mat3 translationMatrix; - -uniform vec4 uFrame; -uniform vec4 uTransform; +uniform mat3 uTransform; varying vec2 vTextureCoord; @@ -13,8 +11,5 @@ { gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vec2 coord = aTextureCoord; - coord -= uTransform.xy; - coord /= uTransform.zw; - vTextureCoord = coord; + vTextureCoord = (uTransform * vec3(aTextureCoord, 1.0)).xy; } diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 6c7049c..6a3612c 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -87,7 +87,7 @@ */ addChildAt(child, index) { - if (index < 0 || index >= this.children.length) + if (index < 0 || index > this.children.length) { throw new Error(`${child}addChildAt: The index ${index} supplied is out of bounds ${this.children.length}`); } diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index bab0a01..455d59c 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -141,6 +141,12 @@ */ this._updateID = 0; + + /** + * Extra field for extra plugins. May contain clamp settings and some matrices + * @type {Object} + */ + this.transform = null; } /** diff --git a/src/extras/TextureTransform.js b/src/extras/TextureTransform.js new file mode 100644 index 0000000..88648b6 --- /dev/null +++ b/src/extras/TextureTransform.js @@ -0,0 +1,115 @@ +import { default as Matrix } from '../core/math/Matrix'; + +const tempMat = new Matrix(); + +/** + * class controls uv transform and frame clamp for texture + */ +export default class TextureTransform { + /** + * + * @param {PIXI.Texture} texture observed texture + * @param {number} [clampMargin] Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + * @constructor + */ + constructor(texture, clampMargin) + { + this._texture = texture; + + this.mapCoord = new Matrix(); + + this.uClampFrame = new Float32Array(4); + + this.uClampOffset = new Float32Array(2); + + this._lastTextureID = -1; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to 1.5 if you tex ture has repeated right and bottom lines, that leads to smoother borders + * + * @default 0 + * @member {number} + */ + this.clampOffset = 0; + + /** + * Changes frame clamping + * Works with TilingSprite and Mesh + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + */ + this.clampMargin = (typeof clampMargin === 'undefined') ? 0.5 : clampMargin; + } + + /** + * texture property + * @member {PIXI.Texture} + * @memberof PIXI.TextureTransform + */ + get texture() + { + return this._texture; + } + + /** + * sets texture value + * @param {PIXI.Texture} value texture to be set + */ + set texture(value) + { + this._texture = value; + this._lastTextureID = -1; + } + + /** + * updates matrices if texture was changed + * @param {boolean} forceUpdate if true, matrices will be updated any case + */ + update(forceUpdate) + { + const tex = this.texture; + + if (!tex || !tex.valid) + { + return; + } + + if (!forceUpdate + && this._lastTextureID === this.texture._updateID) + { + return; + } + + this._lastTextureID = this.texture._updateID; + + const uvs = this.texture._uvs; + + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + + const orig = tex.orig; + const trim = tex.trim; + + if (trim) + { + tempMat.set(orig.width / trim.width, 0, 0, orig.height / trim.height, + -trim.x / trim.width, -trim.y / trim.height); + this.mapCoord.append(tempMat); + } + + const texBase = tex.baseTexture; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase.resolution; + const offset = this.clampOffset; + + frame[0] = (tex._frame.x + margin + offset) / texBase.width; + frame[1] = (tex._frame.y + margin + offset) / texBase.height; + frame[2] = (tex._frame.x + tex._frame.width - margin + offset) / texBase.width; + frame[3] = (tex._frame.y + tex._frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = offset / texBase.realWidth; + this.uClampOffset[1] = offset / texBase.realHeight; + } +} diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 2965caa..af69ffa 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -1,9 +1,7 @@ import * as core from '../core'; -import Texture from '../core/textures/Texture'; import CanvasTinter from '../core/sprites/canvas/CanvasTinter'; -import TilingShader from './webgl/TilingShader'; +import { default as TextureTransform } from './TextureTransform'; -const tempArray = new Float32Array(4); const tempPoint = new core.Point(); /** @@ -25,18 +23,11 @@ super(texture); /** - * The scaling of the image that is being tiled + * Tile transform * - * @member {PIXI.Point} + * @member {PIXI.TransformStatic} */ - this.tileScale = new core.Point(1, 1); - - /** - * The offset position of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0, 0); + this.tileTransform = new core.TransformStatic(); // /// private @@ -57,16 +48,84 @@ this._height = height; /** - * An internal WebGL UV cache. + * Canvas pattern * - * @member {PIXI.TextureUvs} + * @type {CanvasPattern} * @private */ - this._uvs = new core.TextureUvs(); - this._canvasPattern = null; - this._glDatas = []; + /** + * transform that is applied to UV to get the texture coords + * + * @member {PIXI.extras.TextureTransform} + */ + this.uvTransform = texture.transform || new TextureTransform(texture); + } + /** + * Changes frame clamping in corresponding textureTransform, shortcut + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * + * @default 0.5 + * @member {number} + * @memberof PIXI.TilingSprite + */ + get clampMargin() + { + return this.uvTransform.clampMargin; + } + + /** + * setter for clampMargin + * + * @param {number} value assigned value + */ + set clampMargin(value) + { + this.uvTransform.clampMargin = value; + this.uvTransform.update(true); + } + + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.ObservablePoint} + * @memberof PIXI.DisplayObject# + */ + get tileScale() + { + return this.tileTransform.scale; + } + + /** + * Copies the point to the scale of the tiled image. + * + * @param {PIXI.Point|PIXI.ObservablePoint} value - The value to set to. + */ + set tileScale(value) + { + this.tileTransform.scale.copy(value); + } + + /** + * The offset of the image that is being tiled + * + * @member {PIXI.ObservablePoint} + * @memberof PIXI.TilingSprite# + */ + get tilePosition() + { + return this.tileTransform.position; + } + + /** + * Copies the point to the position of the tiled image. + * + * @param {PIXI.Point|PIXI.ObservablePoint} value - The value to set to. + */ + set tilePosition(value) + { + this.tileTransform.position.copy(value); } /** @@ -74,7 +133,10 @@ */ _onTextureUpdate() { - return; + if (this.uvTransform) + { + this.uvTransform.texture = this._texture; + } } /** @@ -88,84 +150,16 @@ // tweak our texture temporarily.. const texture = this._texture; - if (!texture || !texture._uvs) + if (!texture || !texture.valid) { return; } - // get rid of any thing that may be batching. - renderer.flush(); + this.tileTransform.updateLocalTransform(); + this.uvTransform.update(); - const gl = renderer.gl; - let glData = this._glDatas[renderer.CONTEXT_UID]; - - if (!glData) - { - glData = { - shader: new TilingShader(gl), - quad: new core.Quad(gl), - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space - // before transforming the sprite coords.. - const vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = (this._width) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = (this._width) * (1 - this.anchor.x); - vertices[5] = vertices[7] = this._height * (1 - this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - const textureUvs = texture._uvs; - const textureWidth = texture._frame.width; - const textureHeight = texture._frame.height; - const textureBaseWidth = texture.baseTexture.width; - const textureBaseHeight = texture.baseTexture.height; - - const uPixelSize = glData.shader.uniforms.uPixelSize; - - uPixelSize[0] = 1.0 / textureBaseWidth; - uPixelSize[1] = 1.0 / textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - const uFrame = glData.shader.uniforms.uFrame; - - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - const uTransform = glData.shader.uniforms.uTransform; - - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = (textureBaseWidth / this._width) * this.tileScale.x; - uTransform[3] = (textureBaseHeight / this._height) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - const color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode(this.blendMode); - glData.quad.draw(); + renderer.setObjectRenderer(renderer.plugins.tilingSprite); + renderer.plugins.tilingSprite.render(this); } /** @@ -247,6 +241,38 @@ } /** + * Gets the local bounds of the sprite object. + * + * @param {PIXI.Rectangle} rect - The output rectangle. + * @return {PIXI.Rectangle} The bounds. + */ + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if (this.children.length === 0) + { + this._bounds.minX = -this._width * this.anchor._x; + this._bounds.minY = -this._height * this.anchor._y; + this._bounds.maxX = this._width; + this._bounds.maxY = this._height; + + if (!rect) + { + if (!this._localBoundsRect) + { + this._localBoundsRect = new core.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + + return super.getLocalBounds.call(this, rect); + } + + /** * Returns the framing rectangle of the sprite as a Rectangle object * * @return {PIXI.Rectangle} the framing rectangle @@ -358,10 +384,7 @@ super.destroy(); this.tileScale = null; - this._tileScaleOffset = null; this.tilePosition = null; - - this._uvs = null; } /** @@ -376,7 +399,7 @@ */ static from(source, width, height) { - return new TilingSprite(Texture.from(source), width, height); + return new TilingSprite(core.Texture.from(source), width, height); } /** diff --git a/src/extras/index.js b/src/extras/index.js index 73bed41..7fe38b3 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -1,8 +1,10 @@ /** * @namespace PIXI.extras */ +export { default as TextureTransform } from './TextureTransform'; export { default as MovieClip } from './MovieClip'; export { default as TilingSprite } from './TilingSprite'; +export { default as TilingSpriteRenderer } from './webgl/TilingSpriteRenderer'; export { default as BitmapText } from './BitmapText'; export { default as cacheAsBitmap } from './cacheAsBitmap'; diff --git a/src/extras/webgl/TilingShader.js b/src/extras/webgl/TilingShader.js deleted file mode 100644 index 1e63a14..0000000 --- a/src/extras/webgl/TilingShader.js +++ /dev/null @@ -1,22 +0,0 @@ -import Shader from '../../core/Shader'; -const glslify = require('glslify'); // eslint-disable-line no-undef - -/** - * @class - * @extends PIXI.Shader - * @memberof PIXI.mesh - */ -export default class TilingShader extends Shader -{ - /** - * @param {WebGLRenderingContext} gl - The WebGL rendering context. - */ - constructor(gl) - { - super( - gl, - glslify('./tilingSprite.vert'), - glslify('./tilingSprite.frag') - ); - } -} diff --git a/src/extras/webgl/TilingSpriteRenderer.js b/src/extras/webgl/TilingSpriteRenderer.js new file mode 100644 index 0000000..b74d861 --- /dev/null +++ b/src/extras/webgl/TilingSpriteRenderer.js @@ -0,0 +1,146 @@ +import * as core from '../../core'; +import { WRAP_MODES } from '../../core/const'; + +const glslify = require('glslify'); // eslint-disable-line no-undef + +const tempMat = new core.Matrix(); +const tempArray = new Float32Array(4); + +/** + * WebGL renderer plugin for tiling sprites + */ +export class TilingSpriteRenderer extends core.ObjectRenderer { + + /** + * constructor for renderer + * + * @param {WebGLRenderer} renderer The renderer this tiling awesomeness works for. + */ + constructor(renderer) + { + super(renderer); + + this.shader = null; + this.simpleShader = null; + this.quad = null; + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + const gl = this.renderer.gl; + + this.shader = new core.Shader(gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite.frag')); + this.simpleShader = new core.Shader(gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite_simple.frag')); + + this.quad = new core.Quad(gl); + this.quad.initVao(this.shader); + } + + /** + * + * @param {PIXI.extras.TilingSprite} ts tilingSprite to be rendered + */ + render(ts) + { + const quad = this.quad; + let vertices = quad.vertices; + + vertices[0] = vertices[6] = (ts._width) * -ts.anchor.x; + vertices[1] = vertices[3] = ts._height * -ts.anchor.y; + + vertices[2] = vertices[4] = (ts._width) * (1.0 - ts.anchor.x); + vertices[5] = vertices[7] = ts._height * (1.0 - ts.anchor.y); + + vertices = quad.uvs; + + vertices[0] = vertices[6] = -ts.anchor.x; + vertices[1] = vertices[3] = -ts.anchor.y; + + vertices[2] = vertices[4] = 1.0 - ts.anchor.x; + vertices[5] = vertices[7] = 1.0 - ts.anchor.y; + + quad.upload(); + + const renderer = this.renderer; + const tex = ts._texture; + const baseTex = tex.baseTexture; + const lt = ts.tileTransform.localTransform; + const uv = ts.uvTransform; + let isSimple = baseTex.isPowerOfTwo + && tex.frame.width === baseTex.width && tex.frame.height === baseTex.height; + + // auto, force repeat wrapMode for big tiling textures + if (isSimple) + { + if (!baseTex._glTextures[renderer.CONTEXT_UID]) + { + if (baseTex.wrapMode === WRAP_MODES.CLAMP) + { + baseTex.wrapMode = WRAP_MODES.REPEAT; + } + } + else + { + isSimple = baseTex.wrapMode !== WRAP_MODES.CLAMP; + } + } + + const shader = isSimple ? this.simpleShader : this.shader; + + renderer.bindShader(shader); + + const w = tex.width; + const h = tex.height; + const W = ts._width; + const H = ts._height; + + tempMat.set(lt.a * w / W, + lt.b * w / H, + lt.c * h / W, + lt.d * h / H, + lt.tx / W, + lt.ty / H); + + // that part is the same as above: + // tempMat.identity(); + // tempMat.scale(tex.width, tex.height); + // tempMat.prepend(lt); + // tempMat.scale(1.0 / ts._width, 1.0 / ts._height); + + tempMat.invert(); + if (isSimple) + { + tempMat.append(uv.mapCoord); + } + else + { + shader.uniforms.uMapCoord = uv.mapCoord.toArray(true); + shader.uniforms.uClampFrame = uv.uClampFrame; + shader.uniforms.uClampOffset = uv.uClampOffset; + } + shader.uniforms.uTransform = tempMat.toArray(true); + + const color = tempArray; + + core.utils.hex2rgb(ts.tint, color); + color[3] = ts.worldAlpha; + shader.uniforms.uColor = color; + shader.uniforms.translationMatrix = ts.transform.worldTransform.toArray(true); + + renderer.bindTexture(tex); + renderer.setBlendMode(ts.blendMode); + + quad.draw(); + } +} + +core.WebGLRenderer.registerPlugin('tilingSprite', TilingSpriteRenderer); diff --git a/src/extras/webgl/tilingSprite.frag b/src/extras/webgl/tilingSprite.frag index 3996251..3caa383 100644 --- a/src/extras/webgl/tilingSprite.frag +++ b/src/extras/webgl/tilingSprite.frag @@ -2,14 +2,15 @@ uniform sampler2D uSampler; uniform vec4 uColor; -uniform vec4 uFrame; -uniform vec2 uPixelSize; +uniform mat3 uMapCoord; +uniform vec4 uClampFrame; +uniform vec2 uClampOffset; void main(void) { - vec2 coord = mod(vTextureCoord, uFrame.zw); - coord = clamp(coord, uPixelSize, uFrame.zw - uPixelSize); - coord += uFrame.xy; + vec2 coord = mod(vTextureCoord - uClampOffset, vec2(1.0, 1.0)) + uClampOffset; + coord = (uMapCoord * vec3(coord, 1.0)).xy; + coord = clamp(coord, uClampFrame.xy, uClampFrame.zw); vec4 sample = texture2D(uSampler, coord); vec4 color = vec4(uColor.rgb * uColor.a, uColor.a); diff --git a/src/extras/webgl/tilingSprite.vert b/src/extras/webgl/tilingSprite.vert index bb78293..acc096c 100644 --- a/src/extras/webgl/tilingSprite.vert +++ b/src/extras/webgl/tilingSprite.vert @@ -3,9 +3,7 @@ uniform mat3 projectionMatrix; uniform mat3 translationMatrix; - -uniform vec4 uFrame; -uniform vec4 uTransform; +uniform mat3 uTransform; varying vec2 vTextureCoord; @@ -13,8 +11,5 @@ { gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vec2 coord = aTextureCoord; - coord -= uTransform.xy; - coord /= uTransform.zw; - vTextureCoord = coord; + vTextureCoord = (uTransform * vec3(aTextureCoord, 1.0)).xy; } diff --git a/src/extras/webgl/tilingSprite_simple.frag b/src/extras/webgl/tilingSprite_simple.frag new file mode 100644 index 0000000..6675bf3 --- /dev/null +++ b/src/extras/webgl/tilingSprite_simple.frag @@ -0,0 +1,11 @@ +varying vec2 vTextureCoord; + +uniform sampler2D uSampler; +uniform vec4 uColor; + +void main(void) +{ + vec4 sample = texture2D(uSampler, vTextureCoord); + vec4 color = vec4(uColor.rgb * uColor.a, uColor.a); + gl_FragColor = sample * color; +}