diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 0669e79..c2294f9 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,7 +500,7 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.spriteRenderer.flush(); + renderer.objectRenderers.sprite.flush(); renderer.filterManager.pushFilter(this._filterBlock); } diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 0669e79..c2294f9 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,7 +500,7 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.spriteRenderer.flush(); + renderer.objectRenderers.sprite.flush(); renderer.filterManager.pushFilter(this._filterBlock); } diff --git a/src/core/display/Sprite.js b/src/core/display/Sprite.js deleted file mode 100644 index e4e9d01..0000000 --- a/src/core/display/Sprite.js +++ /dev/null @@ -1,461 +0,0 @@ -var math = require('../math'), - Texture = require('../textures/Texture'), - DisplayObjectContainer = require('./DisplayObjectContainer'), - CanvasTinter = require('../renderers/canvas/utils/CanvasTinter'), - utils = require('../utils'), - CONST = require('../const'); - -/** - * The Sprite object is the base for all textured objects that are rendered to the screen - * - * A sprite can be created directly from an image like this: - * - * ```js - * var sprite = new Sprite.fromImage('assets/image.png'); - * ``` - * - * @class Sprite - * @extends DisplayObjectContainer - * @namespace PIXI - * @param texture {Texture} The texture for this sprite - */ -function Sprite(texture) -{ - DisplayObjectContainer.call(this); - - /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting than anchor to 0.5,0.5 means the textures origin is centered - * Setting the anchor to 1,1 would mean the textures origin points will be the bottom right corner - * - * @member {Point} - */ - this.anchor = new math.Point(); - - /** - * The texture that the sprite is using - * - * @member {Texture} - * @private - */ - this._texture = null; - - /** - * The width of the sprite (this is initially set by the texture) - * - * @member {number} - * @private - */ - this._width = 0; - - /** - * The height of the sprite (this is initially set by the texture) - * - * @member {number} - * @private - */ - this._height = 0; - - /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the sprite. Set to CONST.blendModes.NORMAL to remove any blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. - * - * @member {AbstractFilter} - */ - this.shader = null; - - this.renderable = true; - - // call texture setter - this.texture = texture || Texture.EMPTY; -} - -Sprite.prototype.destroy = function (destroyTexture, destroyBaseTexture) -{ - DisplayObjectContainer.prototype.destroy.call(this); - - this.anchor = null; - - if (destroyTexture) - { - this._texture.destroy(destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// constructor -Sprite.prototype = Object.create(DisplayObjectContainer.prototype); -Sprite.prototype.constructor = Sprite; -module.exports = Sprite; - -Object.defineProperties(Sprite.prototype, { - /** - * The width of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - width: { - get: function () - { - return this.scale.x * this.texture.frame.width; - }, - set: function (value) - { - this.scale.x = value / this.texture.frame.width; - this._width = value; - } - }, - - /** - * The height of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - height: { - get: function () - { - return this.scale.y * this.texture.frame.height; - }, - set: function (value) - { - this.scale.y = value / this.texture.frame.height; - this._height = value; - } - }, - - /** - * The height of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - texture: { - get: function () - { - return this._texture; - }, - set: function (value) - { - if (this._texture === value) - { - return; - } - - this._texture = value; - this.cachedTint = 0xFFFFFF; - - if (value) - { - // wait for the texture to load - if (value.baseTexture.hasLoaded) - { - this._onTextureUpdate(); - } - else - { - value.once('update', this._onTextureUpdate.bind(this)); - } - } - } - }, -}); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = this._width / this.texture.frame.width; - } - - if (this._height) - { - this.scale.y = this._height / this.texture.frame.height; - } -}; - -Sprite.prototype._renderWebGL = function (renderer) -{ - - // this is where content gets renderd.. - // watch this space for a little render state manager.. - renderer.setObjectRenderer(renderer.spriteRenderer); - renderer.spriteRenderer.render(this); -}; - -/** - * Returns the bounds of the Sprite as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param matrix {Matrix} the transformation matrix of the sprite - * @return {Rectangle} the framing rectangle - */ -Sprite.prototype.getBounds = function (matrix) -{ - var width = this.texture.frame.width; - var height = this.texture.frame.height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = matrix || this.worldTransform ; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var minX, - maxX, - minY, - maxY; - - if(b === 0 && c === 0) - { - // scale may be negative! - if (a < 0) - { - a *= -1; - } - - if (d < 0) - { - d *= -1; - } - - // this means there is no rotation going on right? RIGHT? - // if thats the case then we can avoid checking the bound values! yay - minX = a * w1 + tx; - maxX = a * w0 + tx; - minY = d * h1 + ty; - maxY = d * h0 + ty; - } - else - { - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {CanvasRenderer} The renderer -*/ -Sprite.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || this.texture.crop.width <= 0 || this.texture.crop.height <= 0) - { - return; - } - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - renderer.context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - // Ignore null sources - if (this.texture.valid) - { - var resolution = this.texture.baseTexture.resolution / renderer.resolution; - - renderer.context.globalAlpha = this.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for this texture - if (renderer.smoothProperty && renderer.scaleMode !== this.texture.baseTexture.scaleMode) - { - renderer.scaleMode = this.texture.baseTexture.scaleMode; - renderer.context[renderer.smoothProperty] = (renderer.scaleMode === CONST.scaleModes.LINEAR); - } - - // If the texture is trimmed we offset by the trim x/y, otherwise we use the frame dimensions - var dx = (this.texture.trim ? this.texture.trim.x : 0) - (this.anchor.x * this.texture.trim.width); - var dy = (this.texture.trim ? this.texture.trim.y : 0) - (this.anchor.y * this.texture.trim.height); - - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - this.worldTransform.a, - this.worldTransform.b, - this.worldTransform.c, - this.worldTransform.d, - (this.worldTransform.tx * renderer.resolution) | 0, - (this.worldTransform.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - this.worldTransform.a, - this.worldTransform.b, - this.worldTransform.c, - this.worldTransform.d, - this.worldTransform.tx * renderer.resolution, - this.worldTransform.ty * renderer.resolution - ); - } - - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - // TODO clean up caching - how to clean up the caches? - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - - renderer.context.drawImage( - this.tintedTexture, - 0, - 0, - this.texture.crop.width, - this.texture.crop.height, - dx / resolution, - dy / resolution, - this.texture.crop.width / resolution, - this.texture.crop.height / resolution - ); - } - else - { - renderer.context.drawImage( - this.texture.baseTexture.source, - this.texture.crop.x, - this.texture.crop.y, - this.texture.crop.width, - this.texture.crop.height, - dx / resolution, - dy / resolution, - this.texture.crop.width / resolution, - this.texture.crop.height / resolution - ); - } - } - - for (var i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -// some helper functions.. - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {String} The frame Id of the texture in the cache - * @return {Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache' + this); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {String} The image url of the texture - * @return {Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 0669e79..c2294f9 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,7 +500,7 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.spriteRenderer.flush(); + renderer.objectRenderers.sprite.flush(); renderer.filterManager.pushFilter(this._filterBlock); } diff --git a/src/core/display/Sprite.js b/src/core/display/Sprite.js deleted file mode 100644 index e4e9d01..0000000 --- a/src/core/display/Sprite.js +++ /dev/null @@ -1,461 +0,0 @@ -var math = require('../math'), - Texture = require('../textures/Texture'), - DisplayObjectContainer = require('./DisplayObjectContainer'), - CanvasTinter = require('../renderers/canvas/utils/CanvasTinter'), - utils = require('../utils'), - CONST = require('../const'); - -/** - * The Sprite object is the base for all textured objects that are rendered to the screen - * - * A sprite can be created directly from an image like this: - * - * ```js - * var sprite = new Sprite.fromImage('assets/image.png'); - * ``` - * - * @class Sprite - * @extends DisplayObjectContainer - * @namespace PIXI - * @param texture {Texture} The texture for this sprite - */ -function Sprite(texture) -{ - DisplayObjectContainer.call(this); - - /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting than anchor to 0.5,0.5 means the textures origin is centered - * Setting the anchor to 1,1 would mean the textures origin points will be the bottom right corner - * - * @member {Point} - */ - this.anchor = new math.Point(); - - /** - * The texture that the sprite is using - * - * @member {Texture} - * @private - */ - this._texture = null; - - /** - * The width of the sprite (this is initially set by the texture) - * - * @member {number} - * @private - */ - this._width = 0; - - /** - * The height of the sprite (this is initially set by the texture) - * - * @member {number} - * @private - */ - this._height = 0; - - /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the sprite. Set to CONST.blendModes.NORMAL to remove any blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. - * - * @member {AbstractFilter} - */ - this.shader = null; - - this.renderable = true; - - // call texture setter - this.texture = texture || Texture.EMPTY; -} - -Sprite.prototype.destroy = function (destroyTexture, destroyBaseTexture) -{ - DisplayObjectContainer.prototype.destroy.call(this); - - this.anchor = null; - - if (destroyTexture) - { - this._texture.destroy(destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// constructor -Sprite.prototype = Object.create(DisplayObjectContainer.prototype); -Sprite.prototype.constructor = Sprite; -module.exports = Sprite; - -Object.defineProperties(Sprite.prototype, { - /** - * The width of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - width: { - get: function () - { - return this.scale.x * this.texture.frame.width; - }, - set: function (value) - { - this.scale.x = value / this.texture.frame.width; - this._width = value; - } - }, - - /** - * The height of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - height: { - get: function () - { - return this.scale.y * this.texture.frame.height; - }, - set: function (value) - { - this.scale.y = value / this.texture.frame.height; - this._height = value; - } - }, - - /** - * The height of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - texture: { - get: function () - { - return this._texture; - }, - set: function (value) - { - if (this._texture === value) - { - return; - } - - this._texture = value; - this.cachedTint = 0xFFFFFF; - - if (value) - { - // wait for the texture to load - if (value.baseTexture.hasLoaded) - { - this._onTextureUpdate(); - } - else - { - value.once('update', this._onTextureUpdate.bind(this)); - } - } - } - }, -}); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = this._width / this.texture.frame.width; - } - - if (this._height) - { - this.scale.y = this._height / this.texture.frame.height; - } -}; - -Sprite.prototype._renderWebGL = function (renderer) -{ - - // this is where content gets renderd.. - // watch this space for a little render state manager.. - renderer.setObjectRenderer(renderer.spriteRenderer); - renderer.spriteRenderer.render(this); -}; - -/** - * Returns the bounds of the Sprite as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param matrix {Matrix} the transformation matrix of the sprite - * @return {Rectangle} the framing rectangle - */ -Sprite.prototype.getBounds = function (matrix) -{ - var width = this.texture.frame.width; - var height = this.texture.frame.height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = matrix || this.worldTransform ; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var minX, - maxX, - minY, - maxY; - - if(b === 0 && c === 0) - { - // scale may be negative! - if (a < 0) - { - a *= -1; - } - - if (d < 0) - { - d *= -1; - } - - // this means there is no rotation going on right? RIGHT? - // if thats the case then we can avoid checking the bound values! yay - minX = a * w1 + tx; - maxX = a * w0 + tx; - minY = d * h1 + ty; - maxY = d * h0 + ty; - } - else - { - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {CanvasRenderer} The renderer -*/ -Sprite.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || this.texture.crop.width <= 0 || this.texture.crop.height <= 0) - { - return; - } - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - renderer.context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - // Ignore null sources - if (this.texture.valid) - { - var resolution = this.texture.baseTexture.resolution / renderer.resolution; - - renderer.context.globalAlpha = this.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for this texture - if (renderer.smoothProperty && renderer.scaleMode !== this.texture.baseTexture.scaleMode) - { - renderer.scaleMode = this.texture.baseTexture.scaleMode; - renderer.context[renderer.smoothProperty] = (renderer.scaleMode === CONST.scaleModes.LINEAR); - } - - // If the texture is trimmed we offset by the trim x/y, otherwise we use the frame dimensions - var dx = (this.texture.trim ? this.texture.trim.x : 0) - (this.anchor.x * this.texture.trim.width); - var dy = (this.texture.trim ? this.texture.trim.y : 0) - (this.anchor.y * this.texture.trim.height); - - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - this.worldTransform.a, - this.worldTransform.b, - this.worldTransform.c, - this.worldTransform.d, - (this.worldTransform.tx * renderer.resolution) | 0, - (this.worldTransform.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - this.worldTransform.a, - this.worldTransform.b, - this.worldTransform.c, - this.worldTransform.d, - this.worldTransform.tx * renderer.resolution, - this.worldTransform.ty * renderer.resolution - ); - } - - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - // TODO clean up caching - how to clean up the caches? - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - - renderer.context.drawImage( - this.tintedTexture, - 0, - 0, - this.texture.crop.width, - this.texture.crop.height, - dx / resolution, - dy / resolution, - this.texture.crop.width / resolution, - this.texture.crop.height / resolution - ); - } - else - { - renderer.context.drawImage( - this.texture.baseTexture.source, - this.texture.crop.x, - this.texture.crop.y, - this.texture.crop.width, - this.texture.crop.height, - dx / resolution, - dy / resolution, - this.texture.crop.width / resolution, - this.texture.crop.height / resolution - ); - } - } - - for (var i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -// some helper functions.. - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {String} The frame Id of the texture in the cache - * @return {Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache' + this); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {String} The image url of the texture - * @return {Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/display/SpriteBatch.js b/src/core/display/SpriteBatch.js deleted file mode 100644 index c196fef..0000000 --- a/src/core/display/SpriteBatch.js +++ /dev/null @@ -1,183 +0,0 @@ -var DisplayObjectContainer = require('./DisplayObjectContainer'), - WebGLFastSpriteBatch = require('../renderers/webgl/utils/WebGLFastSpriteBatch'); - -/** - * The SpriteBatch class is a really fast version of the DisplayObjectContainer built solely for speed, - * so use when you need a lot of sprites or particles. The tradeoff of the SpriteBatch is that advanced - * functionality will not work. SpriteBatch implements only the basic object transform (position, scale, rotation). - * Any other functionality like tinting, masking, etc will not work on sprites in this batch. - * - * It's extremely easy to use : - * - * ```js - * var container = new SpriteBatch(); - * - * for(var i = 0; i < 100; ++i) - * { - * var sprite = new PIXI.Sprite.fromImage("myImage.png"); - * container.addChild(sprite); - * } - * ``` - * - * And here you have a hundred sprites that will be renderer at the speed of light. - * - * @class - * @namespace PIXI - */ - -//TODO RENAME to PARTICLE CONTAINER? -function SpriteBatch() -{ - DisplayObjectContainer.call(this); -} - -SpriteBatch.prototype = Object.create(DisplayObjectContainer.prototype); -SpriteBatch.prototype.constructor = SpriteBatch; -module.exports = SpriteBatch; - -/** - * Updates the object transform for rendering - * - * @private - */ -SpriteBatch.prototype.updateTransform = function () -{ - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.DisplayObjectContainer.prototype.updateTransform.call( this ); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} The webgl renderer - * @private - */ -SpriteBatch.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || !this.children.length) - { - return; - } - - renderer.spriteBatch.stop(); - - renderer.shaderManager.setShader(renderer.shaderManager.fastShader); - - renderer.fastSpriteBatch.begin(this); - renderer.fastSpriteBatch.render(this); - - renderer.spriteBatch.start(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} The canvas renderer - * @private - */ -SpriteBatch.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || !this.children.length) - { - return; - } - - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - var frame = child.texture.frame; - - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) - { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) - { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx, - transform.ty - ); - - isRotated = false; - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5) | 0, - ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5) | 0, - frame.width * child.scale.x, - frame.height * child.scale.y - ); - } - else - { - if (!isRotated) - { - isRotated = true; - } - - child.displayObjectUpdateTransform(); - - var childTransform = child.worldTransform; - - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx | 0, - childTransform.ty | 0 - ); - } - else - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx, - childTransform.ty - ); - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - ((child.anchor.x) * (-frame.width) + 0.5) | 0, - ((child.anchor.y) * (-frame.height) + 0.5) | 0, - frame.width, - frame.height - ); - } - } -}; diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 0669e79..c2294f9 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,7 +500,7 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.spriteRenderer.flush(); + renderer.objectRenderers.sprite.flush(); renderer.filterManager.pushFilter(this._filterBlock); } diff --git a/src/core/display/Sprite.js b/src/core/display/Sprite.js deleted file mode 100644 index e4e9d01..0000000 --- a/src/core/display/Sprite.js +++ /dev/null @@ -1,461 +0,0 @@ -var math = require('../math'), - Texture = require('../textures/Texture'), - DisplayObjectContainer = require('./DisplayObjectContainer'), - CanvasTinter = require('../renderers/canvas/utils/CanvasTinter'), - utils = require('../utils'), - CONST = require('../const'); - -/** - * The Sprite object is the base for all textured objects that are rendered to the screen - * - * A sprite can be created directly from an image like this: - * - * ```js - * var sprite = new Sprite.fromImage('assets/image.png'); - * ``` - * - * @class Sprite - * @extends DisplayObjectContainer - * @namespace PIXI - * @param texture {Texture} The texture for this sprite - */ -function Sprite(texture) -{ - DisplayObjectContainer.call(this); - - /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting than anchor to 0.5,0.5 means the textures origin is centered - * Setting the anchor to 1,1 would mean the textures origin points will be the bottom right corner - * - * @member {Point} - */ - this.anchor = new math.Point(); - - /** - * The texture that the sprite is using - * - * @member {Texture} - * @private - */ - this._texture = null; - - /** - * The width of the sprite (this is initially set by the texture) - * - * @member {number} - * @private - */ - this._width = 0; - - /** - * The height of the sprite (this is initially set by the texture) - * - * @member {number} - * @private - */ - this._height = 0; - - /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the sprite. Set to CONST.blendModes.NORMAL to remove any blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. - * - * @member {AbstractFilter} - */ - this.shader = null; - - this.renderable = true; - - // call texture setter - this.texture = texture || Texture.EMPTY; -} - -Sprite.prototype.destroy = function (destroyTexture, destroyBaseTexture) -{ - DisplayObjectContainer.prototype.destroy.call(this); - - this.anchor = null; - - if (destroyTexture) - { - this._texture.destroy(destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// constructor -Sprite.prototype = Object.create(DisplayObjectContainer.prototype); -Sprite.prototype.constructor = Sprite; -module.exports = Sprite; - -Object.defineProperties(Sprite.prototype, { - /** - * The width of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - width: { - get: function () - { - return this.scale.x * this.texture.frame.width; - }, - set: function (value) - { - this.scale.x = value / this.texture.frame.width; - this._width = value; - } - }, - - /** - * The height of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - height: { - get: function () - { - return this.scale.y * this.texture.frame.height; - }, - set: function (value) - { - this.scale.y = value / this.texture.frame.height; - this._height = value; - } - }, - - /** - * The height of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - texture: { - get: function () - { - return this._texture; - }, - set: function (value) - { - if (this._texture === value) - { - return; - } - - this._texture = value; - this.cachedTint = 0xFFFFFF; - - if (value) - { - // wait for the texture to load - if (value.baseTexture.hasLoaded) - { - this._onTextureUpdate(); - } - else - { - value.once('update', this._onTextureUpdate.bind(this)); - } - } - } - }, -}); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = this._width / this.texture.frame.width; - } - - if (this._height) - { - this.scale.y = this._height / this.texture.frame.height; - } -}; - -Sprite.prototype._renderWebGL = function (renderer) -{ - - // this is where content gets renderd.. - // watch this space for a little render state manager.. - renderer.setObjectRenderer(renderer.spriteRenderer); - renderer.spriteRenderer.render(this); -}; - -/** - * Returns the bounds of the Sprite as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param matrix {Matrix} the transformation matrix of the sprite - * @return {Rectangle} the framing rectangle - */ -Sprite.prototype.getBounds = function (matrix) -{ - var width = this.texture.frame.width; - var height = this.texture.frame.height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = matrix || this.worldTransform ; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var minX, - maxX, - minY, - maxY; - - if(b === 0 && c === 0) - { - // scale may be negative! - if (a < 0) - { - a *= -1; - } - - if (d < 0) - { - d *= -1; - } - - // this means there is no rotation going on right? RIGHT? - // if thats the case then we can avoid checking the bound values! yay - minX = a * w1 + tx; - maxX = a * w0 + tx; - minY = d * h1 + ty; - maxY = d * h0 + ty; - } - else - { - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {CanvasRenderer} The renderer -*/ -Sprite.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || this.texture.crop.width <= 0 || this.texture.crop.height <= 0) - { - return; - } - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - renderer.context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - // Ignore null sources - if (this.texture.valid) - { - var resolution = this.texture.baseTexture.resolution / renderer.resolution; - - renderer.context.globalAlpha = this.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for this texture - if (renderer.smoothProperty && renderer.scaleMode !== this.texture.baseTexture.scaleMode) - { - renderer.scaleMode = this.texture.baseTexture.scaleMode; - renderer.context[renderer.smoothProperty] = (renderer.scaleMode === CONST.scaleModes.LINEAR); - } - - // If the texture is trimmed we offset by the trim x/y, otherwise we use the frame dimensions - var dx = (this.texture.trim ? this.texture.trim.x : 0) - (this.anchor.x * this.texture.trim.width); - var dy = (this.texture.trim ? this.texture.trim.y : 0) - (this.anchor.y * this.texture.trim.height); - - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - this.worldTransform.a, - this.worldTransform.b, - this.worldTransform.c, - this.worldTransform.d, - (this.worldTransform.tx * renderer.resolution) | 0, - (this.worldTransform.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - this.worldTransform.a, - this.worldTransform.b, - this.worldTransform.c, - this.worldTransform.d, - this.worldTransform.tx * renderer.resolution, - this.worldTransform.ty * renderer.resolution - ); - } - - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - // TODO clean up caching - how to clean up the caches? - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - - renderer.context.drawImage( - this.tintedTexture, - 0, - 0, - this.texture.crop.width, - this.texture.crop.height, - dx / resolution, - dy / resolution, - this.texture.crop.width / resolution, - this.texture.crop.height / resolution - ); - } - else - { - renderer.context.drawImage( - this.texture.baseTexture.source, - this.texture.crop.x, - this.texture.crop.y, - this.texture.crop.width, - this.texture.crop.height, - dx / resolution, - dy / resolution, - this.texture.crop.width / resolution, - this.texture.crop.height / resolution - ); - } - } - - for (var i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -// some helper functions.. - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {String} The frame Id of the texture in the cache - * @return {Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache' + this); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {String} The image url of the texture - * @return {Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/display/SpriteBatch.js b/src/core/display/SpriteBatch.js deleted file mode 100644 index c196fef..0000000 --- a/src/core/display/SpriteBatch.js +++ /dev/null @@ -1,183 +0,0 @@ -var DisplayObjectContainer = require('./DisplayObjectContainer'), - WebGLFastSpriteBatch = require('../renderers/webgl/utils/WebGLFastSpriteBatch'); - -/** - * The SpriteBatch class is a really fast version of the DisplayObjectContainer built solely for speed, - * so use when you need a lot of sprites or particles. The tradeoff of the SpriteBatch is that advanced - * functionality will not work. SpriteBatch implements only the basic object transform (position, scale, rotation). - * Any other functionality like tinting, masking, etc will not work on sprites in this batch. - * - * It's extremely easy to use : - * - * ```js - * var container = new SpriteBatch(); - * - * for(var i = 0; i < 100; ++i) - * { - * var sprite = new PIXI.Sprite.fromImage("myImage.png"); - * container.addChild(sprite); - * } - * ``` - * - * And here you have a hundred sprites that will be renderer at the speed of light. - * - * @class - * @namespace PIXI - */ - -//TODO RENAME to PARTICLE CONTAINER? -function SpriteBatch() -{ - DisplayObjectContainer.call(this); -} - -SpriteBatch.prototype = Object.create(DisplayObjectContainer.prototype); -SpriteBatch.prototype.constructor = SpriteBatch; -module.exports = SpriteBatch; - -/** - * Updates the object transform for rendering - * - * @private - */ -SpriteBatch.prototype.updateTransform = function () -{ - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.DisplayObjectContainer.prototype.updateTransform.call( this ); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} The webgl renderer - * @private - */ -SpriteBatch.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || !this.children.length) - { - return; - } - - renderer.spriteBatch.stop(); - - renderer.shaderManager.setShader(renderer.shaderManager.fastShader); - - renderer.fastSpriteBatch.begin(this); - renderer.fastSpriteBatch.render(this); - - renderer.spriteBatch.start(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} The canvas renderer - * @private - */ -SpriteBatch.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || !this.children.length) - { - return; - } - - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - var frame = child.texture.frame; - - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) - { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) - { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx, - transform.ty - ); - - isRotated = false; - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5) | 0, - ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5) | 0, - frame.width * child.scale.x, - frame.height * child.scale.y - ); - } - else - { - if (!isRotated) - { - isRotated = true; - } - - child.displayObjectUpdateTransform(); - - var childTransform = child.worldTransform; - - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx | 0, - childTransform.ty | 0 - ); - } - else - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx, - childTransform.ty - ); - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - ((child.anchor.x) * (-frame.width) + 0.5) | 0, - ((child.anchor.y) * (-frame.height) + 0.5) | 0, - frame.width, - frame.height - ); - } - } -}; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js index 70d28b1..baab0f8 100644 --- a/src/core/primitives/Graphics.js +++ b/src/core/primitives/Graphics.js @@ -738,17 +738,17 @@ return; } - + */ - + if (this.glDirty) { this.dirty = true; this.glDirty = false; } - renderer.setObjectRenderer(renderer.graphicsRenderer); - renderer.graphicsRenderer.render(this); + renderer.setObjectRenderer(renderer.objectRenderers.graphics); + renderer.objectRenderers.graphics.render(this); }; diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 0669e79..c2294f9 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,7 +500,7 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.spriteRenderer.flush(); + renderer.objectRenderers.sprite.flush(); renderer.filterManager.pushFilter(this._filterBlock); } diff --git a/src/core/display/Sprite.js b/src/core/display/Sprite.js deleted file mode 100644 index e4e9d01..0000000 --- a/src/core/display/Sprite.js +++ /dev/null @@ -1,461 +0,0 @@ -var math = require('../math'), - Texture = require('../textures/Texture'), - DisplayObjectContainer = require('./DisplayObjectContainer'), - CanvasTinter = require('../renderers/canvas/utils/CanvasTinter'), - utils = require('../utils'), - CONST = require('../const'); - -/** - * The Sprite object is the base for all textured objects that are rendered to the screen - * - * A sprite can be created directly from an image like this: - * - * ```js - * var sprite = new Sprite.fromImage('assets/image.png'); - * ``` - * - * @class Sprite - * @extends DisplayObjectContainer - * @namespace PIXI - * @param texture {Texture} The texture for this sprite - */ -function Sprite(texture) -{ - DisplayObjectContainer.call(this); - - /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting than anchor to 0.5,0.5 means the textures origin is centered - * Setting the anchor to 1,1 would mean the textures origin points will be the bottom right corner - * - * @member {Point} - */ - this.anchor = new math.Point(); - - /** - * The texture that the sprite is using - * - * @member {Texture} - * @private - */ - this._texture = null; - - /** - * The width of the sprite (this is initially set by the texture) - * - * @member {number} - * @private - */ - this._width = 0; - - /** - * The height of the sprite (this is initially set by the texture) - * - * @member {number} - * @private - */ - this._height = 0; - - /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the sprite. Set to CONST.blendModes.NORMAL to remove any blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. - * - * @member {AbstractFilter} - */ - this.shader = null; - - this.renderable = true; - - // call texture setter - this.texture = texture || Texture.EMPTY; -} - -Sprite.prototype.destroy = function (destroyTexture, destroyBaseTexture) -{ - DisplayObjectContainer.prototype.destroy.call(this); - - this.anchor = null; - - if (destroyTexture) - { - this._texture.destroy(destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// constructor -Sprite.prototype = Object.create(DisplayObjectContainer.prototype); -Sprite.prototype.constructor = Sprite; -module.exports = Sprite; - -Object.defineProperties(Sprite.prototype, { - /** - * The width of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - width: { - get: function () - { - return this.scale.x * this.texture.frame.width; - }, - set: function (value) - { - this.scale.x = value / this.texture.frame.width; - this._width = value; - } - }, - - /** - * The height of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - height: { - get: function () - { - return this.scale.y * this.texture.frame.height; - }, - set: function (value) - { - this.scale.y = value / this.texture.frame.height; - this._height = value; - } - }, - - /** - * The height of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - texture: { - get: function () - { - return this._texture; - }, - set: function (value) - { - if (this._texture === value) - { - return; - } - - this._texture = value; - this.cachedTint = 0xFFFFFF; - - if (value) - { - // wait for the texture to load - if (value.baseTexture.hasLoaded) - { - this._onTextureUpdate(); - } - else - { - value.once('update', this._onTextureUpdate.bind(this)); - } - } - } - }, -}); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = this._width / this.texture.frame.width; - } - - if (this._height) - { - this.scale.y = this._height / this.texture.frame.height; - } -}; - -Sprite.prototype._renderWebGL = function (renderer) -{ - - // this is where content gets renderd.. - // watch this space for a little render state manager.. - renderer.setObjectRenderer(renderer.spriteRenderer); - renderer.spriteRenderer.render(this); -}; - -/** - * Returns the bounds of the Sprite as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param matrix {Matrix} the transformation matrix of the sprite - * @return {Rectangle} the framing rectangle - */ -Sprite.prototype.getBounds = function (matrix) -{ - var width = this.texture.frame.width; - var height = this.texture.frame.height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = matrix || this.worldTransform ; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var minX, - maxX, - minY, - maxY; - - if(b === 0 && c === 0) - { - // scale may be negative! - if (a < 0) - { - a *= -1; - } - - if (d < 0) - { - d *= -1; - } - - // this means there is no rotation going on right? RIGHT? - // if thats the case then we can avoid checking the bound values! yay - minX = a * w1 + tx; - maxX = a * w0 + tx; - minY = d * h1 + ty; - maxY = d * h0 + ty; - } - else - { - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {CanvasRenderer} The renderer -*/ -Sprite.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || this.texture.crop.width <= 0 || this.texture.crop.height <= 0) - { - return; - } - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - renderer.context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - // Ignore null sources - if (this.texture.valid) - { - var resolution = this.texture.baseTexture.resolution / renderer.resolution; - - renderer.context.globalAlpha = this.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for this texture - if (renderer.smoothProperty && renderer.scaleMode !== this.texture.baseTexture.scaleMode) - { - renderer.scaleMode = this.texture.baseTexture.scaleMode; - renderer.context[renderer.smoothProperty] = (renderer.scaleMode === CONST.scaleModes.LINEAR); - } - - // If the texture is trimmed we offset by the trim x/y, otherwise we use the frame dimensions - var dx = (this.texture.trim ? this.texture.trim.x : 0) - (this.anchor.x * this.texture.trim.width); - var dy = (this.texture.trim ? this.texture.trim.y : 0) - (this.anchor.y * this.texture.trim.height); - - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - this.worldTransform.a, - this.worldTransform.b, - this.worldTransform.c, - this.worldTransform.d, - (this.worldTransform.tx * renderer.resolution) | 0, - (this.worldTransform.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - this.worldTransform.a, - this.worldTransform.b, - this.worldTransform.c, - this.worldTransform.d, - this.worldTransform.tx * renderer.resolution, - this.worldTransform.ty * renderer.resolution - ); - } - - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - // TODO clean up caching - how to clean up the caches? - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - - renderer.context.drawImage( - this.tintedTexture, - 0, - 0, - this.texture.crop.width, - this.texture.crop.height, - dx / resolution, - dy / resolution, - this.texture.crop.width / resolution, - this.texture.crop.height / resolution - ); - } - else - { - renderer.context.drawImage( - this.texture.baseTexture.source, - this.texture.crop.x, - this.texture.crop.y, - this.texture.crop.width, - this.texture.crop.height, - dx / resolution, - dy / resolution, - this.texture.crop.width / resolution, - this.texture.crop.height / resolution - ); - } - } - - for (var i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -// some helper functions.. - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {String} The frame Id of the texture in the cache - * @return {Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache' + this); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {String} The image url of the texture - * @return {Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/display/SpriteBatch.js b/src/core/display/SpriteBatch.js deleted file mode 100644 index c196fef..0000000 --- a/src/core/display/SpriteBatch.js +++ /dev/null @@ -1,183 +0,0 @@ -var DisplayObjectContainer = require('./DisplayObjectContainer'), - WebGLFastSpriteBatch = require('../renderers/webgl/utils/WebGLFastSpriteBatch'); - -/** - * The SpriteBatch class is a really fast version of the DisplayObjectContainer built solely for speed, - * so use when you need a lot of sprites or particles. The tradeoff of the SpriteBatch is that advanced - * functionality will not work. SpriteBatch implements only the basic object transform (position, scale, rotation). - * Any other functionality like tinting, masking, etc will not work on sprites in this batch. - * - * It's extremely easy to use : - * - * ```js - * var container = new SpriteBatch(); - * - * for(var i = 0; i < 100; ++i) - * { - * var sprite = new PIXI.Sprite.fromImage("myImage.png"); - * container.addChild(sprite); - * } - * ``` - * - * And here you have a hundred sprites that will be renderer at the speed of light. - * - * @class - * @namespace PIXI - */ - -//TODO RENAME to PARTICLE CONTAINER? -function SpriteBatch() -{ - DisplayObjectContainer.call(this); -} - -SpriteBatch.prototype = Object.create(DisplayObjectContainer.prototype); -SpriteBatch.prototype.constructor = SpriteBatch; -module.exports = SpriteBatch; - -/** - * Updates the object transform for rendering - * - * @private - */ -SpriteBatch.prototype.updateTransform = function () -{ - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.DisplayObjectContainer.prototype.updateTransform.call( this ); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} The webgl renderer - * @private - */ -SpriteBatch.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || !this.children.length) - { - return; - } - - renderer.spriteBatch.stop(); - - renderer.shaderManager.setShader(renderer.shaderManager.fastShader); - - renderer.fastSpriteBatch.begin(this); - renderer.fastSpriteBatch.render(this); - - renderer.spriteBatch.start(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} The canvas renderer - * @private - */ -SpriteBatch.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || !this.children.length) - { - return; - } - - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - var frame = child.texture.frame; - - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) - { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) - { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx, - transform.ty - ); - - isRotated = false; - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5) | 0, - ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5) | 0, - frame.width * child.scale.x, - frame.height * child.scale.y - ); - } - else - { - if (!isRotated) - { - isRotated = true; - } - - child.displayObjectUpdateTransform(); - - var childTransform = child.worldTransform; - - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx | 0, - childTransform.ty | 0 - ); - } - else - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx, - childTransform.ty - ); - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - ((child.anchor.x) * (-frame.width) + 0.5) | 0, - ((child.anchor.y) * (-frame.height) + 0.5) | 0, - frame.width, - frame.height - ); - } - } -}; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js index 70d28b1..baab0f8 100644 --- a/src/core/primitives/Graphics.js +++ b/src/core/primitives/Graphics.js @@ -738,17 +738,17 @@ return; } - + */ - + if (this.glDirty) { this.dirty = true; this.glDirty = false; } - renderer.setObjectRenderer(renderer.graphicsRenderer); - renderer.graphicsRenderer.render(this); + renderer.setObjectRenderer(renderer.objectRenderers.graphics); + renderer.objectRenderers.graphics.render(this); }; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js new file mode 100644 index 0000000..55dc7a5 --- /dev/null +++ b/src/core/primitives/GraphicsRenderer.js @@ -0,0 +1,874 @@ +var utils = require('../../../utils'), + math = require('../../../math'), + CONST = require('../../../const'), + ObjectRenderer = require('../renderers/webgl/utils/ObjectRenderer'), + WebGLRenderer = require('../renderers/webgl/WebGLRenderer'), + WebGLGraphicsData = require('./WebGLGraphicsData'); + +/** + * Renders the graphics object. + * + * @class + * @private + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this object renderer works for. + */ +function GraphicsRenderer(renderer) +{ + ObjectRenderer.call(this, renderer); + + this.graphicsDataPool = []; +} + +GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); +GraphicsRenderer.prototype.constructor = GraphicsRenderer; +module.exports = GraphicsRenderer; + +WebGLRenderer.registerObjectRenderer('graphics', GraphicsRenderer); + +/** + * Destroys this renderer. + * + */ +GraphicsRenderer.prototype.destroy = function () { + ObjectRenderer.prototype.destroy.call(this); + + this.graphicsDataPool = null; +}; + +/** + * Renders a graphics object. + * + * @param graphics {Graphics} The graphics object to render. + */ +GraphicsRenderer.prototype.render = function(graphics) +{ + var renderer = this.renderer; + var gl = renderer.gl; + + var projection = renderer.projection, + offset = renderer.offset, + shader = renderer.shaderManager.primitiveShader, + webGLData; + + if (graphics.dirty) + { + this.updateGraphics(graphics, gl); + } + + var webGL = graphics._webGL[gl.id]; + + // This could be speeded up for sure! + + renderer.blendModeManager.setBlendMode( graphics.blendMode ); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.stencilManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.stencilManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.primitiveShader; + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniform1f(shader.uniforms.flipY._location, 1); + + gl.uniform2f(shader.uniforms.projectionVector._location, projection.x, -projection.y); + gl.uniform2f(shader.uniforms.offsetVector._location, -offset.x, -offset.y); + + gl.uniform3fv(shader.uniforms.tint._location, utils.hex2rgb(graphics.tint)); + + gl.uniform1f(shader.uniforms.alpha._location, graphics.worldAlpha); + + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.attributes.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); + gl.vertexAttribPointer(shader.attributes.aColor, 4, gl.FLOAT, false,4 * 6, 2 * 4); + + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + } + } +} + +/** + * Updates the graphics object + * + * @private + * @param graphicsData {Graphics} The graphics object to update + * @param gl {WebGLContext} the current WebGL drawing context + */ +GraphicsRenderer.prototype.updateGraphics = function(graphics) +{ + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[gl.id]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[gl.id] = {lastIndex:0, data:[], gl:gl}; + } + + // flag the graphics as not dirty as we are about to update it... + graphics.dirty = false; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty) + { + graphics.clearDirty = false; + + // lop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + graphicsData.reset(); + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + if (data.type === CONST.SHAPES.POLY) + { + // need to add the points the the graphics object.. + data.points = data.shape.points.slice(); + if (data.shape.closed) + { + // close the poly if the value is true! + if (data.points[0] !== data.points[data.points.length-2] || data.points[1] !== data.points[data.points.length-1]) + { + data.points.push(data.points[0], data.points[1]); + } + } + + // MAKE SURE WE HAVE THE CORRECT TYPE.. + if (data.fill) + { + if (data.points.length >= 6) + { + if (data.points.length < 6 * 2) + { + webGLData = this.switchMode(webGL, 0); + + var canDrawUsingSimple = this.buildPoly(data, webGLData); + // console.log(canDrawUsingSimple); + + if (!canDrawUsingSimple) + { + // console.log("<>>>") + webGLData = this.switchMode(webGL, 1); + this.buildComplexPoly(data, webGLData); + } + + } + else + { + webGLData = this.switchMode(webGL, 1); + this.buildComplexPoly(data, webGLData); + } + } + } + + if (data.lineWidth > 0) + { + webGLData = this.switchMode(webGL, 0); + this.buildLine(data, webGLData); + } + } + else + { + webGLData = this.switchMode(webGL, 0); + + if (data.type === CONST.SHAPES.RECT) + { + this.buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + this.buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + this.buildRoundedRectangle(data, webGLData); + } + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } +} + +/** + * + * + * @private + * @param webGL {WebGLContext} + * @param type {number} + */ +GraphicsRenderer.prototype.switchMode = function (webGL, type) +{ + var webGLData; + + if (!webGL.data.length) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(webGL.gl); + webGLData.mode = type; + webGL.data.push(webGLData); + } + else + { + webGLData = webGL.data[webGL.data.length-1]; + + if (webGLData.mode !== type || type === 1) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(webGL.gl); + webGLData.mode = type; + webGL.data.push(webGLData); + } + } + + webGLData.dirty = true; + + return webGLData; +}; + +/** + * Builds a rectangle to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildRectangle = function (graphicsData, webGLData) +{ + // --- // + // need to convert points to a nice regular data + // + var rectData = graphicsData.shape; + var x = rectData.x; + var y = rectData.y; + var width = rectData.width; + var height = rectData.height; + + if (graphicsData.fill) + { + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var verts = webGLData.points; + var indices = webGLData.indices; + + var vertPos = verts.length/6; + + // start + verts.push(x, y); + verts.push(r, g, b, alpha); + + verts.push(x + width, y); + verts.push(r, g, b, alpha); + + verts.push(x , y + height); + verts.push(r, g, b, alpha); + + verts.push(x + width, y + height); + verts.push(r, g, b, alpha); + + // insert 2 dead triangles.. + indices.push(vertPos, vertPos, vertPos+1, vertPos+2, vertPos+3, vertPos+3); + } + + if (graphicsData.lineWidth) + { + var tempPoints = graphicsData.points; + + graphicsData.points = [x, y, + x + width, y, + x + width, y + height, + x, y + height, + x, y]; + + + this.buildLine(graphicsData, webGLData); + + graphicsData.points = tempPoints; + } +}; + +/** + * Builds a rounded rectangle to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildRoundedRectangle = function (graphicsData, webGLData) +{ + var rrectData = graphicsData.shape; + var x = rrectData.x; + var y = rrectData.y; + var width = rrectData.width; + var height = rrectData.height; + + var radius = rrectData.radius; + + var recPoints = []; + recPoints.push(x, y + radius); + recPoints = recPoints.concat(this.quadraticBezierCurve(x, y + height - radius, x, y + height, x + radius, y + height)); + recPoints = recPoints.concat(this.quadraticBezierCurve(x + width - radius, y + height, x + width, y + height, x + width, y + height - radius)); + recPoints = recPoints.concat(this.quadraticBezierCurve(x + width, y + radius, x + width, y, x + width - radius, y)); + recPoints = recPoints.concat(this.quadraticBezierCurve(x + radius, y, x, y, x, y + radius)); + + if (graphicsData.fill) + { + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var verts = webGLData.points; + var indices = webGLData.indices; + + var vecPos = verts.length/6; + + var triangles = utils.PolyK.Triangulate(recPoints); + + // + + var i = 0; + for (i = 0; i < triangles.length; i+=3) + { + indices.push(triangles[i] + vecPos); + indices.push(triangles[i] + vecPos); + indices.push(triangles[i+1] + vecPos); + indices.push(triangles[i+2] + vecPos); + indices.push(triangles[i+2] + vecPos); + } + + for (i = 0; i < recPoints.length; i++) + { + verts.push(recPoints[i], recPoints[++i], r, g, b, alpha); + } + } + + if (graphicsData.lineWidth) + { + var tempPoints = graphicsData.points; + + graphicsData.points = recPoints; + + this.buildLine(graphicsData, webGLData); + + graphicsData.points = tempPoints; + } +}; + +/** + * Calculate the points for a quadratic bezier curve. (helper function..) + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @private + * @param fromX {number} Origin point x + * @param fromY {number} Origin point x + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {number[]} + */ +GraphicsRenderer.prototype.quadraticBezierCurve = function (fromX, fromY, cpX, cpY, toX, toY) +{ + + var xa, + ya, + xb, + yb, + x, + y, + n = 20, + points = []; + + function getPt(n1 , n2, perc) { + var diff = n2 - n1; + + return n1 + ( diff * perc ); + } + + var j = 0; + for (var i = 0; i <= n; i++ ) { + j = i / n; + + // The Green Line + xa = getPt( fromX , cpX , j ); + ya = getPt( fromY , cpY , j ); + xb = getPt( cpX , toX , j ); + yb = getPt( cpY , toY , j ); + + // The Black Dot + x = getPt( xa , xb , j ); + y = getPt( ya , yb , j ); + + points.push(x, y); + } + return points; +}; + +/** + * Builds a circle to draw + * + * @private + * @param graphicsData {Graphics} The graphics object to draw + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildCircle = function (graphicsData, webGLData) +{ + // need to convert points to a nice regular data + var circleData = graphicsData.shape; + var x = circleData.x; + var y = circleData.y; + var width; + var height; + + // TODO - bit hacky?? + if (graphicsData.type === CONST.SHAPES.CIRC) + { + width = circleData.radius; + height = circleData.radius; + } + else + { + width = circleData.width; + height = circleData.height; + } + + var totalSegs = 40; + var seg = (Math.PI * 2) / totalSegs ; + + var i = 0; + + if (graphicsData.fill) + { + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var verts = webGLData.points; + var indices = webGLData.indices; + + var vecPos = verts.length/6; + + indices.push(vecPos); + + for (i = 0; i < totalSegs + 1 ; i++) + { + verts.push(x,y, r, g, b, alpha); + + verts.push(x + Math.sin(seg * i) * width, + y + Math.cos(seg * i) * height, + r, g, b, alpha); + + indices.push(vecPos++, vecPos++); + } + + indices.push(vecPos-1); + } + + if (graphicsData.lineWidth) + { + var tempPoints = graphicsData.points; + + graphicsData.points = []; + + for (i = 0; i < totalSegs + 1; i++) + { + graphicsData.points.push(x + Math.sin(seg * i) * width, + y + Math.cos(seg * i) * height); + } + + this.buildLine(graphicsData, webGLData); + + graphicsData.points = tempPoints; + } +}; + +/** + * Builds a line to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildLine = function (graphicsData, webGLData) +{ + // TODO OPTIMISE! + var i = 0; + var points = graphicsData.points; + + if (points.length === 0) + { + return; + } + + // if the line width is an odd number add 0.5 to align to a whole pixel + if (graphicsData.lineWidth%2) + { + for (i = 0; i < points.length; i++) + { + points[i] += 0.5; + } + } + + // get first and last point.. figure out the middle! + var firstPoint = new math.Point(points[0], points[1]); + var lastPoint = new math.Point(points[points.length - 2], points[points.length - 1]); + + // if the first point is the last point - gonna have issues :) + if (firstPoint.x === lastPoint.x && firstPoint.y === lastPoint.y) + { + // need to clone as we are going to slightly modify the shape.. + points = points.slice(); + + points.pop(); + points.pop(); + + lastPoint = new math.Point(points[points.length - 2], points[points.length - 1]); + + var midPointX = lastPoint.x + (firstPoint.x - lastPoint.x) *0.5; + var midPointY = lastPoint.y + (firstPoint.y - lastPoint.y) *0.5; + + points.unshift(midPointX, midPointY); + points.push(midPointX, midPointY); + } + + var verts = webGLData.points; + var indices = webGLData.indices; + var length = points.length / 2; + var indexCount = points.length; + var indexStart = verts.length/6; + + // DRAW the Line + var width = graphicsData.lineWidth / 2; + + // sort color + var color = utils.hex2rgb(graphicsData.lineColor); + var alpha = graphicsData.lineAlpha; + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var px, py, p1x, p1y, p2x, p2y, p3x, p3y; + var perpx, perpy, perp2x, perp2y, perp3x, perp3y; + var a1, b1, c1, a2, b2, c2; + var denom, pdist, dist; + + p1x = points[0]; + p1y = points[1]; + + p2x = points[2]; + p2y = points[3]; + + perpx = -(p1y - p2y); + perpy = p1x - p2x; + + dist = Math.sqrt(perpx*perpx + perpy*perpy); + + perpx /= dist; + perpy /= dist; + perpx *= width; + perpy *= width; + + // start + verts.push(p1x - perpx , p1y - perpy, + r, g, b, alpha); + + verts.push(p1x + perpx , p1y + perpy, + r, g, b, alpha); + + for (i = 1; i < length-1; i++) + { + p1x = points[(i-1)*2]; + p1y = points[(i-1)*2 + 1]; + + p2x = points[(i)*2]; + p2y = points[(i)*2 + 1]; + + p3x = points[(i+1)*2]; + p3y = points[(i+1)*2 + 1]; + + perpx = -(p1y - p2y); + perpy = p1x - p2x; + + dist = Math.sqrt(perpx*perpx + perpy*perpy); + perpx /= dist; + perpy /= dist; + perpx *= width; + perpy *= width; + + perp2x = -(p2y - p3y); + perp2y = p2x - p3x; + + dist = Math.sqrt(perp2x*perp2x + perp2y*perp2y); + perp2x /= dist; + perp2y /= dist; + perp2x *= width; + perp2y *= width; + + a1 = (-perpy + p1y) - (-perpy + p2y); + b1 = (-perpx + p2x) - (-perpx + p1x); + c1 = (-perpx + p1x) * (-perpy + p2y) - (-perpx + p2x) * (-perpy + p1y); + a2 = (-perp2y + p3y) - (-perp2y + p2y); + b2 = (-perp2x + p2x) - (-perp2x + p3x); + c2 = (-perp2x + p3x) * (-perp2y + p2y) - (-perp2x + p2x) * (-perp2y + p3y); + + denom = a1*b2 - a2*b1; + + if (Math.abs(denom) < 0.1 ) + { + + denom+=10.1; + verts.push(p2x - perpx , p2y - perpy, + r, g, b, alpha); + + verts.push(p2x + perpx , p2y + perpy, + r, g, b, alpha); + + continue; + } + + px = (b1*c2 - b2*c1)/denom; + py = (a2*c1 - a1*c2)/denom; + + + pdist = (px -p2x) * (px -p2x) + (py -p2y) + (py -p2y); + + + if (pdist > 140 * 140) + { + perp3x = perpx - perp2x; + perp3y = perpy - perp2y; + + dist = Math.sqrt(perp3x*perp3x + perp3y*perp3y); + perp3x /= dist; + perp3y /= dist; + perp3x *= width; + perp3y *= width; + + verts.push(p2x - perp3x, p2y -perp3y); + verts.push(r, g, b, alpha); + + verts.push(p2x + perp3x, p2y +perp3y); + verts.push(r, g, b, alpha); + + verts.push(p2x - perp3x, p2y -perp3y); + verts.push(r, g, b, alpha); + + indexCount++; + } + else + { + + verts.push(px , py); + verts.push(r, g, b, alpha); + + verts.push(p2x - (px-p2x), p2y - (py - p2y)); + verts.push(r, g, b, alpha); + } + } + + p1x = points[(length-2)*2]; + p1y = points[(length-2)*2 + 1]; + + p2x = points[(length-1)*2]; + p2y = points[(length-1)*2 + 1]; + + perpx = -(p1y - p2y); + perpy = p1x - p2x; + + dist = Math.sqrt(perpx*perpx + perpy*perpy); + perpx /= dist; + perpy /= dist; + perpx *= width; + perpy *= width; + + verts.push(p2x - perpx , p2y - perpy); + verts.push(r, g, b, alpha); + + verts.push(p2x + perpx , p2y + perpy); + verts.push(r, g, b, alpha); + + indices.push(indexStart); + + for (i = 0; i < indexCount; i++) + { + indices.push(indexStart++); + } + + indices.push(indexStart-1); +}; + +/** + * Builds a complex polygon to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildComplexPoly = function (graphicsData, webGLData) +{ + //TODO - no need to copy this as it gets turned into a FLoat32Array anyways.. + var points = graphicsData.points.slice(); + + if (points.length < 6) + { + return; + } + + // get first and last point.. figure out the middle! + var indices = webGLData.indices; + webGLData.points = points; + webGLData.alpha = graphicsData.fillAlpha; + webGLData.color = utils.hex2rgb(graphicsData.fillColor); + + // calclate the bounds.. + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + var x,y; + + // get size.. + for (var i = 0; i < points.length; i+=2) + { + x = points[i]; + y = points[i+1]; + + minX = x < minX ? x : minX; + maxX = x > maxX ? x : maxX; + + minY = y < minY ? y : minY; + maxY = y > maxY ? y : maxY; + } + + // add a quad to the end cos there is no point making another buffer! + points.push(minX, minY, + maxX, minY, + maxX, maxY, + minX, maxY); + + // push a quad onto the end.. + + //TODO - this aint needed! + var length = points.length / 2; + for (i = 0; i < length; i++) + { + indices.push( i ); + } + +}; + +/** + * Builds a polygon to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildPoly = function (graphicsData, webGLData) +{ + var points = graphicsData.points; + + if (points.length < 6) + { + return; + } + + // get first and last point.. figure out the middle! + var verts = webGLData.points; + var indices = webGLData.indices; + + var length = points.length / 2; + + // sort color + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var triangles = utils.PolyK.Triangulate(points); + + if (!triangles) { + return false; + } + + var vertPos = verts.length / 6; + + var i = 0; + + for (i = 0; i < triangles.length; i+=3) + { + indices.push(triangles[i] + vertPos); + indices.push(triangles[i] + vertPos); + indices.push(triangles[i+1] + vertPos); + indices.push(triangles[i+2] +vertPos); + indices.push(triangles[i+2] + vertPos); + } + + for (i = 0; i < length; i++) + { + verts.push(points[i * 2], points[i * 2 + 1], + r, g, b, alpha); + } + + return true; +}; diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 0669e79..c2294f9 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,7 +500,7 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.spriteRenderer.flush(); + renderer.objectRenderers.sprite.flush(); renderer.filterManager.pushFilter(this._filterBlock); } diff --git a/src/core/display/Sprite.js b/src/core/display/Sprite.js deleted file mode 100644 index e4e9d01..0000000 --- a/src/core/display/Sprite.js +++ /dev/null @@ -1,461 +0,0 @@ -var math = require('../math'), - Texture = require('../textures/Texture'), - DisplayObjectContainer = require('./DisplayObjectContainer'), - CanvasTinter = require('../renderers/canvas/utils/CanvasTinter'), - utils = require('../utils'), - CONST = require('../const'); - -/** - * The Sprite object is the base for all textured objects that are rendered to the screen - * - * A sprite can be created directly from an image like this: - * - * ```js - * var sprite = new Sprite.fromImage('assets/image.png'); - * ``` - * - * @class Sprite - * @extends DisplayObjectContainer - * @namespace PIXI - * @param texture {Texture} The texture for this sprite - */ -function Sprite(texture) -{ - DisplayObjectContainer.call(this); - - /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting than anchor to 0.5,0.5 means the textures origin is centered - * Setting the anchor to 1,1 would mean the textures origin points will be the bottom right corner - * - * @member {Point} - */ - this.anchor = new math.Point(); - - /** - * The texture that the sprite is using - * - * @member {Texture} - * @private - */ - this._texture = null; - - /** - * The width of the sprite (this is initially set by the texture) - * - * @member {number} - * @private - */ - this._width = 0; - - /** - * The height of the sprite (this is initially set by the texture) - * - * @member {number} - * @private - */ - this._height = 0; - - /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the sprite. Set to CONST.blendModes.NORMAL to remove any blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. - * - * @member {AbstractFilter} - */ - this.shader = null; - - this.renderable = true; - - // call texture setter - this.texture = texture || Texture.EMPTY; -} - -Sprite.prototype.destroy = function (destroyTexture, destroyBaseTexture) -{ - DisplayObjectContainer.prototype.destroy.call(this); - - this.anchor = null; - - if (destroyTexture) - { - this._texture.destroy(destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// constructor -Sprite.prototype = Object.create(DisplayObjectContainer.prototype); -Sprite.prototype.constructor = Sprite; -module.exports = Sprite; - -Object.defineProperties(Sprite.prototype, { - /** - * The width of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - width: { - get: function () - { - return this.scale.x * this.texture.frame.width; - }, - set: function (value) - { - this.scale.x = value / this.texture.frame.width; - this._width = value; - } - }, - - /** - * The height of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - height: { - get: function () - { - return this.scale.y * this.texture.frame.height; - }, - set: function (value) - { - this.scale.y = value / this.texture.frame.height; - this._height = value; - } - }, - - /** - * The height of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - texture: { - get: function () - { - return this._texture; - }, - set: function (value) - { - if (this._texture === value) - { - return; - } - - this._texture = value; - this.cachedTint = 0xFFFFFF; - - if (value) - { - // wait for the texture to load - if (value.baseTexture.hasLoaded) - { - this._onTextureUpdate(); - } - else - { - value.once('update', this._onTextureUpdate.bind(this)); - } - } - } - }, -}); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = this._width / this.texture.frame.width; - } - - if (this._height) - { - this.scale.y = this._height / this.texture.frame.height; - } -}; - -Sprite.prototype._renderWebGL = function (renderer) -{ - - // this is where content gets renderd.. - // watch this space for a little render state manager.. - renderer.setObjectRenderer(renderer.spriteRenderer); - renderer.spriteRenderer.render(this); -}; - -/** - * Returns the bounds of the Sprite as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param matrix {Matrix} the transformation matrix of the sprite - * @return {Rectangle} the framing rectangle - */ -Sprite.prototype.getBounds = function (matrix) -{ - var width = this.texture.frame.width; - var height = this.texture.frame.height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = matrix || this.worldTransform ; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var minX, - maxX, - minY, - maxY; - - if(b === 0 && c === 0) - { - // scale may be negative! - if (a < 0) - { - a *= -1; - } - - if (d < 0) - { - d *= -1; - } - - // this means there is no rotation going on right? RIGHT? - // if thats the case then we can avoid checking the bound values! yay - minX = a * w1 + tx; - maxX = a * w0 + tx; - minY = d * h1 + ty; - maxY = d * h0 + ty; - } - else - { - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {CanvasRenderer} The renderer -*/ -Sprite.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || this.texture.crop.width <= 0 || this.texture.crop.height <= 0) - { - return; - } - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - renderer.context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - // Ignore null sources - if (this.texture.valid) - { - var resolution = this.texture.baseTexture.resolution / renderer.resolution; - - renderer.context.globalAlpha = this.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for this texture - if (renderer.smoothProperty && renderer.scaleMode !== this.texture.baseTexture.scaleMode) - { - renderer.scaleMode = this.texture.baseTexture.scaleMode; - renderer.context[renderer.smoothProperty] = (renderer.scaleMode === CONST.scaleModes.LINEAR); - } - - // If the texture is trimmed we offset by the trim x/y, otherwise we use the frame dimensions - var dx = (this.texture.trim ? this.texture.trim.x : 0) - (this.anchor.x * this.texture.trim.width); - var dy = (this.texture.trim ? this.texture.trim.y : 0) - (this.anchor.y * this.texture.trim.height); - - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - this.worldTransform.a, - this.worldTransform.b, - this.worldTransform.c, - this.worldTransform.d, - (this.worldTransform.tx * renderer.resolution) | 0, - (this.worldTransform.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - this.worldTransform.a, - this.worldTransform.b, - this.worldTransform.c, - this.worldTransform.d, - this.worldTransform.tx * renderer.resolution, - this.worldTransform.ty * renderer.resolution - ); - } - - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - // TODO clean up caching - how to clean up the caches? - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - - renderer.context.drawImage( - this.tintedTexture, - 0, - 0, - this.texture.crop.width, - this.texture.crop.height, - dx / resolution, - dy / resolution, - this.texture.crop.width / resolution, - this.texture.crop.height / resolution - ); - } - else - { - renderer.context.drawImage( - this.texture.baseTexture.source, - this.texture.crop.x, - this.texture.crop.y, - this.texture.crop.width, - this.texture.crop.height, - dx / resolution, - dy / resolution, - this.texture.crop.width / resolution, - this.texture.crop.height / resolution - ); - } - } - - for (var i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -// some helper functions.. - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {String} The frame Id of the texture in the cache - * @return {Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache' + this); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {String} The image url of the texture - * @return {Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/display/SpriteBatch.js b/src/core/display/SpriteBatch.js deleted file mode 100644 index c196fef..0000000 --- a/src/core/display/SpriteBatch.js +++ /dev/null @@ -1,183 +0,0 @@ -var DisplayObjectContainer = require('./DisplayObjectContainer'), - WebGLFastSpriteBatch = require('../renderers/webgl/utils/WebGLFastSpriteBatch'); - -/** - * The SpriteBatch class is a really fast version of the DisplayObjectContainer built solely for speed, - * so use when you need a lot of sprites or particles. The tradeoff of the SpriteBatch is that advanced - * functionality will not work. SpriteBatch implements only the basic object transform (position, scale, rotation). - * Any other functionality like tinting, masking, etc will not work on sprites in this batch. - * - * It's extremely easy to use : - * - * ```js - * var container = new SpriteBatch(); - * - * for(var i = 0; i < 100; ++i) - * { - * var sprite = new PIXI.Sprite.fromImage("myImage.png"); - * container.addChild(sprite); - * } - * ``` - * - * And here you have a hundred sprites that will be renderer at the speed of light. - * - * @class - * @namespace PIXI - */ - -//TODO RENAME to PARTICLE CONTAINER? -function SpriteBatch() -{ - DisplayObjectContainer.call(this); -} - -SpriteBatch.prototype = Object.create(DisplayObjectContainer.prototype); -SpriteBatch.prototype.constructor = SpriteBatch; -module.exports = SpriteBatch; - -/** - * Updates the object transform for rendering - * - * @private - */ -SpriteBatch.prototype.updateTransform = function () -{ - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.DisplayObjectContainer.prototype.updateTransform.call( this ); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} The webgl renderer - * @private - */ -SpriteBatch.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || !this.children.length) - { - return; - } - - renderer.spriteBatch.stop(); - - renderer.shaderManager.setShader(renderer.shaderManager.fastShader); - - renderer.fastSpriteBatch.begin(this); - renderer.fastSpriteBatch.render(this); - - renderer.spriteBatch.start(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} The canvas renderer - * @private - */ -SpriteBatch.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || !this.children.length) - { - return; - } - - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - var frame = child.texture.frame; - - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) - { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) - { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx, - transform.ty - ); - - isRotated = false; - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5) | 0, - ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5) | 0, - frame.width * child.scale.x, - frame.height * child.scale.y - ); - } - else - { - if (!isRotated) - { - isRotated = true; - } - - child.displayObjectUpdateTransform(); - - var childTransform = child.worldTransform; - - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx | 0, - childTransform.ty | 0 - ); - } - else - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx, - childTransform.ty - ); - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - ((child.anchor.x) * (-frame.width) + 0.5) | 0, - ((child.anchor.y) * (-frame.height) + 0.5) | 0, - frame.width, - frame.height - ); - } - } -}; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js index 70d28b1..baab0f8 100644 --- a/src/core/primitives/Graphics.js +++ b/src/core/primitives/Graphics.js @@ -738,17 +738,17 @@ return; } - + */ - + if (this.glDirty) { this.dirty = true; this.glDirty = false; } - renderer.setObjectRenderer(renderer.graphicsRenderer); - renderer.graphicsRenderer.render(this); + renderer.setObjectRenderer(renderer.objectRenderers.graphics); + renderer.objectRenderers.graphics.render(this); }; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js new file mode 100644 index 0000000..55dc7a5 --- /dev/null +++ b/src/core/primitives/GraphicsRenderer.js @@ -0,0 +1,874 @@ +var utils = require('../../../utils'), + math = require('../../../math'), + CONST = require('../../../const'), + ObjectRenderer = require('../renderers/webgl/utils/ObjectRenderer'), + WebGLRenderer = require('../renderers/webgl/WebGLRenderer'), + WebGLGraphicsData = require('./WebGLGraphicsData'); + +/** + * Renders the graphics object. + * + * @class + * @private + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this object renderer works for. + */ +function GraphicsRenderer(renderer) +{ + ObjectRenderer.call(this, renderer); + + this.graphicsDataPool = []; +} + +GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); +GraphicsRenderer.prototype.constructor = GraphicsRenderer; +module.exports = GraphicsRenderer; + +WebGLRenderer.registerObjectRenderer('graphics', GraphicsRenderer); + +/** + * Destroys this renderer. + * + */ +GraphicsRenderer.prototype.destroy = function () { + ObjectRenderer.prototype.destroy.call(this); + + this.graphicsDataPool = null; +}; + +/** + * Renders a graphics object. + * + * @param graphics {Graphics} The graphics object to render. + */ +GraphicsRenderer.prototype.render = function(graphics) +{ + var renderer = this.renderer; + var gl = renderer.gl; + + var projection = renderer.projection, + offset = renderer.offset, + shader = renderer.shaderManager.primitiveShader, + webGLData; + + if (graphics.dirty) + { + this.updateGraphics(graphics, gl); + } + + var webGL = graphics._webGL[gl.id]; + + // This could be speeded up for sure! + + renderer.blendModeManager.setBlendMode( graphics.blendMode ); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.stencilManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.stencilManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.primitiveShader; + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniform1f(shader.uniforms.flipY._location, 1); + + gl.uniform2f(shader.uniforms.projectionVector._location, projection.x, -projection.y); + gl.uniform2f(shader.uniforms.offsetVector._location, -offset.x, -offset.y); + + gl.uniform3fv(shader.uniforms.tint._location, utils.hex2rgb(graphics.tint)); + + gl.uniform1f(shader.uniforms.alpha._location, graphics.worldAlpha); + + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.attributes.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); + gl.vertexAttribPointer(shader.attributes.aColor, 4, gl.FLOAT, false,4 * 6, 2 * 4); + + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + } + } +} + +/** + * Updates the graphics object + * + * @private + * @param graphicsData {Graphics} The graphics object to update + * @param gl {WebGLContext} the current WebGL drawing context + */ +GraphicsRenderer.prototype.updateGraphics = function(graphics) +{ + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[gl.id]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[gl.id] = {lastIndex:0, data:[], gl:gl}; + } + + // flag the graphics as not dirty as we are about to update it... + graphics.dirty = false; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty) + { + graphics.clearDirty = false; + + // lop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + graphicsData.reset(); + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + if (data.type === CONST.SHAPES.POLY) + { + // need to add the points the the graphics object.. + data.points = data.shape.points.slice(); + if (data.shape.closed) + { + // close the poly if the value is true! + if (data.points[0] !== data.points[data.points.length-2] || data.points[1] !== data.points[data.points.length-1]) + { + data.points.push(data.points[0], data.points[1]); + } + } + + // MAKE SURE WE HAVE THE CORRECT TYPE.. + if (data.fill) + { + if (data.points.length >= 6) + { + if (data.points.length < 6 * 2) + { + webGLData = this.switchMode(webGL, 0); + + var canDrawUsingSimple = this.buildPoly(data, webGLData); + // console.log(canDrawUsingSimple); + + if (!canDrawUsingSimple) + { + // console.log("<>>>") + webGLData = this.switchMode(webGL, 1); + this.buildComplexPoly(data, webGLData); + } + + } + else + { + webGLData = this.switchMode(webGL, 1); + this.buildComplexPoly(data, webGLData); + } + } + } + + if (data.lineWidth > 0) + { + webGLData = this.switchMode(webGL, 0); + this.buildLine(data, webGLData); + } + } + else + { + webGLData = this.switchMode(webGL, 0); + + if (data.type === CONST.SHAPES.RECT) + { + this.buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + this.buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + this.buildRoundedRectangle(data, webGLData); + } + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } +} + +/** + * + * + * @private + * @param webGL {WebGLContext} + * @param type {number} + */ +GraphicsRenderer.prototype.switchMode = function (webGL, type) +{ + var webGLData; + + if (!webGL.data.length) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(webGL.gl); + webGLData.mode = type; + webGL.data.push(webGLData); + } + else + { + webGLData = webGL.data[webGL.data.length-1]; + + if (webGLData.mode !== type || type === 1) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(webGL.gl); + webGLData.mode = type; + webGL.data.push(webGLData); + } + } + + webGLData.dirty = true; + + return webGLData; +}; + +/** + * Builds a rectangle to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildRectangle = function (graphicsData, webGLData) +{ + // --- // + // need to convert points to a nice regular data + // + var rectData = graphicsData.shape; + var x = rectData.x; + var y = rectData.y; + var width = rectData.width; + var height = rectData.height; + + if (graphicsData.fill) + { + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var verts = webGLData.points; + var indices = webGLData.indices; + + var vertPos = verts.length/6; + + // start + verts.push(x, y); + verts.push(r, g, b, alpha); + + verts.push(x + width, y); + verts.push(r, g, b, alpha); + + verts.push(x , y + height); + verts.push(r, g, b, alpha); + + verts.push(x + width, y + height); + verts.push(r, g, b, alpha); + + // insert 2 dead triangles.. + indices.push(vertPos, vertPos, vertPos+1, vertPos+2, vertPos+3, vertPos+3); + } + + if (graphicsData.lineWidth) + { + var tempPoints = graphicsData.points; + + graphicsData.points = [x, y, + x + width, y, + x + width, y + height, + x, y + height, + x, y]; + + + this.buildLine(graphicsData, webGLData); + + graphicsData.points = tempPoints; + } +}; + +/** + * Builds a rounded rectangle to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildRoundedRectangle = function (graphicsData, webGLData) +{ + var rrectData = graphicsData.shape; + var x = rrectData.x; + var y = rrectData.y; + var width = rrectData.width; + var height = rrectData.height; + + var radius = rrectData.radius; + + var recPoints = []; + recPoints.push(x, y + radius); + recPoints = recPoints.concat(this.quadraticBezierCurve(x, y + height - radius, x, y + height, x + radius, y + height)); + recPoints = recPoints.concat(this.quadraticBezierCurve(x + width - radius, y + height, x + width, y + height, x + width, y + height - radius)); + recPoints = recPoints.concat(this.quadraticBezierCurve(x + width, y + radius, x + width, y, x + width - radius, y)); + recPoints = recPoints.concat(this.quadraticBezierCurve(x + radius, y, x, y, x, y + radius)); + + if (graphicsData.fill) + { + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var verts = webGLData.points; + var indices = webGLData.indices; + + var vecPos = verts.length/6; + + var triangles = utils.PolyK.Triangulate(recPoints); + + // + + var i = 0; + for (i = 0; i < triangles.length; i+=3) + { + indices.push(triangles[i] + vecPos); + indices.push(triangles[i] + vecPos); + indices.push(triangles[i+1] + vecPos); + indices.push(triangles[i+2] + vecPos); + indices.push(triangles[i+2] + vecPos); + } + + for (i = 0; i < recPoints.length; i++) + { + verts.push(recPoints[i], recPoints[++i], r, g, b, alpha); + } + } + + if (graphicsData.lineWidth) + { + var tempPoints = graphicsData.points; + + graphicsData.points = recPoints; + + this.buildLine(graphicsData, webGLData); + + graphicsData.points = tempPoints; + } +}; + +/** + * Calculate the points for a quadratic bezier curve. (helper function..) + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @private + * @param fromX {number} Origin point x + * @param fromY {number} Origin point x + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {number[]} + */ +GraphicsRenderer.prototype.quadraticBezierCurve = function (fromX, fromY, cpX, cpY, toX, toY) +{ + + var xa, + ya, + xb, + yb, + x, + y, + n = 20, + points = []; + + function getPt(n1 , n2, perc) { + var diff = n2 - n1; + + return n1 + ( diff * perc ); + } + + var j = 0; + for (var i = 0; i <= n; i++ ) { + j = i / n; + + // The Green Line + xa = getPt( fromX , cpX , j ); + ya = getPt( fromY , cpY , j ); + xb = getPt( cpX , toX , j ); + yb = getPt( cpY , toY , j ); + + // The Black Dot + x = getPt( xa , xb , j ); + y = getPt( ya , yb , j ); + + points.push(x, y); + } + return points; +}; + +/** + * Builds a circle to draw + * + * @private + * @param graphicsData {Graphics} The graphics object to draw + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildCircle = function (graphicsData, webGLData) +{ + // need to convert points to a nice regular data + var circleData = graphicsData.shape; + var x = circleData.x; + var y = circleData.y; + var width; + var height; + + // TODO - bit hacky?? + if (graphicsData.type === CONST.SHAPES.CIRC) + { + width = circleData.radius; + height = circleData.radius; + } + else + { + width = circleData.width; + height = circleData.height; + } + + var totalSegs = 40; + var seg = (Math.PI * 2) / totalSegs ; + + var i = 0; + + if (graphicsData.fill) + { + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var verts = webGLData.points; + var indices = webGLData.indices; + + var vecPos = verts.length/6; + + indices.push(vecPos); + + for (i = 0; i < totalSegs + 1 ; i++) + { + verts.push(x,y, r, g, b, alpha); + + verts.push(x + Math.sin(seg * i) * width, + y + Math.cos(seg * i) * height, + r, g, b, alpha); + + indices.push(vecPos++, vecPos++); + } + + indices.push(vecPos-1); + } + + if (graphicsData.lineWidth) + { + var tempPoints = graphicsData.points; + + graphicsData.points = []; + + for (i = 0; i < totalSegs + 1; i++) + { + graphicsData.points.push(x + Math.sin(seg * i) * width, + y + Math.cos(seg * i) * height); + } + + this.buildLine(graphicsData, webGLData); + + graphicsData.points = tempPoints; + } +}; + +/** + * Builds a line to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildLine = function (graphicsData, webGLData) +{ + // TODO OPTIMISE! + var i = 0; + var points = graphicsData.points; + + if (points.length === 0) + { + return; + } + + // if the line width is an odd number add 0.5 to align to a whole pixel + if (graphicsData.lineWidth%2) + { + for (i = 0; i < points.length; i++) + { + points[i] += 0.5; + } + } + + // get first and last point.. figure out the middle! + var firstPoint = new math.Point(points[0], points[1]); + var lastPoint = new math.Point(points[points.length - 2], points[points.length - 1]); + + // if the first point is the last point - gonna have issues :) + if (firstPoint.x === lastPoint.x && firstPoint.y === lastPoint.y) + { + // need to clone as we are going to slightly modify the shape.. + points = points.slice(); + + points.pop(); + points.pop(); + + lastPoint = new math.Point(points[points.length - 2], points[points.length - 1]); + + var midPointX = lastPoint.x + (firstPoint.x - lastPoint.x) *0.5; + var midPointY = lastPoint.y + (firstPoint.y - lastPoint.y) *0.5; + + points.unshift(midPointX, midPointY); + points.push(midPointX, midPointY); + } + + var verts = webGLData.points; + var indices = webGLData.indices; + var length = points.length / 2; + var indexCount = points.length; + var indexStart = verts.length/6; + + // DRAW the Line + var width = graphicsData.lineWidth / 2; + + // sort color + var color = utils.hex2rgb(graphicsData.lineColor); + var alpha = graphicsData.lineAlpha; + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var px, py, p1x, p1y, p2x, p2y, p3x, p3y; + var perpx, perpy, perp2x, perp2y, perp3x, perp3y; + var a1, b1, c1, a2, b2, c2; + var denom, pdist, dist; + + p1x = points[0]; + p1y = points[1]; + + p2x = points[2]; + p2y = points[3]; + + perpx = -(p1y - p2y); + perpy = p1x - p2x; + + dist = Math.sqrt(perpx*perpx + perpy*perpy); + + perpx /= dist; + perpy /= dist; + perpx *= width; + perpy *= width; + + // start + verts.push(p1x - perpx , p1y - perpy, + r, g, b, alpha); + + verts.push(p1x + perpx , p1y + perpy, + r, g, b, alpha); + + for (i = 1; i < length-1; i++) + { + p1x = points[(i-1)*2]; + p1y = points[(i-1)*2 + 1]; + + p2x = points[(i)*2]; + p2y = points[(i)*2 + 1]; + + p3x = points[(i+1)*2]; + p3y = points[(i+1)*2 + 1]; + + perpx = -(p1y - p2y); + perpy = p1x - p2x; + + dist = Math.sqrt(perpx*perpx + perpy*perpy); + perpx /= dist; + perpy /= dist; + perpx *= width; + perpy *= width; + + perp2x = -(p2y - p3y); + perp2y = p2x - p3x; + + dist = Math.sqrt(perp2x*perp2x + perp2y*perp2y); + perp2x /= dist; + perp2y /= dist; + perp2x *= width; + perp2y *= width; + + a1 = (-perpy + p1y) - (-perpy + p2y); + b1 = (-perpx + p2x) - (-perpx + p1x); + c1 = (-perpx + p1x) * (-perpy + p2y) - (-perpx + p2x) * (-perpy + p1y); + a2 = (-perp2y + p3y) - (-perp2y + p2y); + b2 = (-perp2x + p2x) - (-perp2x + p3x); + c2 = (-perp2x + p3x) * (-perp2y + p2y) - (-perp2x + p2x) * (-perp2y + p3y); + + denom = a1*b2 - a2*b1; + + if (Math.abs(denom) < 0.1 ) + { + + denom+=10.1; + verts.push(p2x - perpx , p2y - perpy, + r, g, b, alpha); + + verts.push(p2x + perpx , p2y + perpy, + r, g, b, alpha); + + continue; + } + + px = (b1*c2 - b2*c1)/denom; + py = (a2*c1 - a1*c2)/denom; + + + pdist = (px -p2x) * (px -p2x) + (py -p2y) + (py -p2y); + + + if (pdist > 140 * 140) + { + perp3x = perpx - perp2x; + perp3y = perpy - perp2y; + + dist = Math.sqrt(perp3x*perp3x + perp3y*perp3y); + perp3x /= dist; + perp3y /= dist; + perp3x *= width; + perp3y *= width; + + verts.push(p2x - perp3x, p2y -perp3y); + verts.push(r, g, b, alpha); + + verts.push(p2x + perp3x, p2y +perp3y); + verts.push(r, g, b, alpha); + + verts.push(p2x - perp3x, p2y -perp3y); + verts.push(r, g, b, alpha); + + indexCount++; + } + else + { + + verts.push(px , py); + verts.push(r, g, b, alpha); + + verts.push(p2x - (px-p2x), p2y - (py - p2y)); + verts.push(r, g, b, alpha); + } + } + + p1x = points[(length-2)*2]; + p1y = points[(length-2)*2 + 1]; + + p2x = points[(length-1)*2]; + p2y = points[(length-1)*2 + 1]; + + perpx = -(p1y - p2y); + perpy = p1x - p2x; + + dist = Math.sqrt(perpx*perpx + perpy*perpy); + perpx /= dist; + perpy /= dist; + perpx *= width; + perpy *= width; + + verts.push(p2x - perpx , p2y - perpy); + verts.push(r, g, b, alpha); + + verts.push(p2x + perpx , p2y + perpy); + verts.push(r, g, b, alpha); + + indices.push(indexStart); + + for (i = 0; i < indexCount; i++) + { + indices.push(indexStart++); + } + + indices.push(indexStart-1); +}; + +/** + * Builds a complex polygon to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildComplexPoly = function (graphicsData, webGLData) +{ + //TODO - no need to copy this as it gets turned into a FLoat32Array anyways.. + var points = graphicsData.points.slice(); + + if (points.length < 6) + { + return; + } + + // get first and last point.. figure out the middle! + var indices = webGLData.indices; + webGLData.points = points; + webGLData.alpha = graphicsData.fillAlpha; + webGLData.color = utils.hex2rgb(graphicsData.fillColor); + + // calclate the bounds.. + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + var x,y; + + // get size.. + for (var i = 0; i < points.length; i+=2) + { + x = points[i]; + y = points[i+1]; + + minX = x < minX ? x : minX; + maxX = x > maxX ? x : maxX; + + minY = y < minY ? y : minY; + maxY = y > maxY ? y : maxY; + } + + // add a quad to the end cos there is no point making another buffer! + points.push(minX, minY, + maxX, minY, + maxX, maxY, + minX, maxY); + + // push a quad onto the end.. + + //TODO - this aint needed! + var length = points.length / 2; + for (i = 0; i < length; i++) + { + indices.push( i ); + } + +}; + +/** + * Builds a polygon to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildPoly = function (graphicsData, webGLData) +{ + var points = graphicsData.points; + + if (points.length < 6) + { + return; + } + + // get first and last point.. figure out the middle! + var verts = webGLData.points; + var indices = webGLData.indices; + + var length = points.length / 2; + + // sort color + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var triangles = utils.PolyK.Triangulate(points); + + if (!triangles) { + return false; + } + + var vertPos = verts.length / 6; + + var i = 0; + + for (i = 0; i < triangles.length; i+=3) + { + indices.push(triangles[i] + vertPos); + indices.push(triangles[i] + vertPos); + indices.push(triangles[i+1] + vertPos); + indices.push(triangles[i+2] +vertPos); + indices.push(triangles[i+2] + vertPos); + } + + for (i = 0; i < length; i++) + { + verts.push(points[i * 2], points[i * 2 + 1], + r, g, b, alpha); + } + + return true; +}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 547bdc7..b0189c5 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,6 +1,4 @@ -var SpriteRenderer = require('./utils/SpriteRenderer'), - GraphicsRenderer = require('./utils/GraphicsRenderer'), - WebGLFastSpriteBatch = require('./utils/WebGLFastSpriteBatch'), +var WebGLFastSpriteBatch = require('./utils/WebGLFastSpriteBatch'), WebGLShaderManager = require('./managers/WebGLShaderManager'), WebGLMaskManager = require('./managers/WebGLMaskManager'), WebGLFilterManager = require('./managers/WebGLFilterManager'), @@ -28,21 +26,21 @@ * @param [options.preserveDrawingBuffer=false] {boolean} enables drawing buffer preservation, enable this if you need to call toDataUrl on the webgl context * @param [options.resolution=1] {number} the resolution of the renderer retina would be 2 */ -function WebGLRenderer(width, height, options) +function WebGLRenderer(width, height, options) { utils.sayHello('webGL'); - if (options) + if (options) { - for (var i in CONST.defaultRenderOptions) + for (var i in CONST.defaultRenderOptions) { - if (typeof options[i] === 'undefined') + if (typeof options[i] === 'undefined') { options[i] = CONST.defaultRenderOptions[i]; } } } - else + else { options = CONST.defaultRenderOptions; } @@ -191,14 +189,6 @@ /** * Manages the rendering of sprites - * @member {SpriteRenderer} - */ - this.spriteRenderer = new SpriteRenderer(this); - - this.graphicsRenderer = new GraphicsRenderer(this); - - /** - * Manages the rendering of sprites * @member {WebGLFastSpriteBatch} */ this.fastSpriteBatch = new WebGLFastSpriteBatch(this); @@ -245,8 +235,17 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - this.currentRenderer = this.spriteRenderer; + /** + * Manages the renderer of specific objects. + * + * @member {object} + */ + this.objectRenderers = {}; + // create an instance of each registered object renderer + for (var o in WebGLRenderer._objectRenderers) { + this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); + } } // constructor @@ -255,21 +254,20 @@ utils.eventTarget.mixin(WebGLRenderer.prototype); -Object.defineProperties(WebGLRenderer.prototype, -{ +Object.defineProperties(WebGLRenderer.prototype, { /** * The background color to fill if not transparent * * @member {number} * @memberof WebGLRenderer# */ - backgroundColor: + backgroundColor: { - get: function () + get: function () { return this._backgroundColor; }, - set: function (val) + set: function (val) { this._backgroundColor = val; utils.hex2rgb(val, this._backgroundColorRgb); @@ -281,12 +279,12 @@ * * @private */ -WebGLRenderer.prototype._initContext = function () +WebGLRenderer.prototype._initContext = function () { var gl = this.view.getContext('webgl', this._contextOptions) || this.view.getContext('experimental-webgl', this._contextOptions); this.gl = gl; - if (!gl) + if (!gl) { // fail, not able to get a context throw new Error('This browser does not support webGL. Try using the canvas renderer'); @@ -312,10 +310,10 @@ * * @param object {DisplayObject} the object to be rendered */ -WebGLRenderer.prototype.render = function (object) +WebGLRenderer.prototype.render = function (object) { // no point rendering if our context has been blown up! - if (this.gl.isContextLost()) + if (this.gl.isContextLost()) { return; } @@ -336,13 +334,13 @@ // make sure we are bound to the main frame buffer gl.bindFramebuffer(gl.FRAMEBUFFER, null); - if (this.clearBeforeRender) + if (this.clearBeforeRender) { - if (this.transparent) + if (this.transparent) { gl.clearColor(0, 0, 0, 0); } - else + else { gl.clearColor(this._backgroundColorRgb[0], this._backgroundColorRgb[1], this._backgroundColorRgb[2], 1); } @@ -360,7 +358,7 @@ * @param projection {Point} The projection * @param buffer {Array} a standard WebGL buffer */ -WebGLRenderer.prototype.renderDisplayObject = function (displayObject, projection, buffer) +WebGLRenderer.prototype.renderDisplayObject = function (displayObject, projection, buffer) { this.blendModeManager.setBlendMode(CONST.blendModes.NORMAL); @@ -386,9 +384,9 @@ this.currentRenderer.flush(); }; -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) +WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) { - if(this.currentRenderer === objectRenderer) + if (this.currentRenderer === objectRenderer) { return; } @@ -396,7 +394,7 @@ this.currentRenderer.stop(); this.currentRenderer = objectRenderer; this.currentRenderer.start(); -} +}; /** * Resizes the webGL view to the specified width and height. @@ -404,7 +402,7 @@ * @param width {number} the new width of the webGL view * @param height {number} the new height of the webGL view */ -WebGLRenderer.prototype.resize = function (width, height) +WebGLRenderer.prototype.resize = function (width, height) { this.width = width * this.resolution; this.height = height * this.resolution; @@ -412,7 +410,7 @@ this.view.width = this.width; this.view.height = this.height; - if (this.autoResize) + if (this.autoResize) { this.view.style.width = this.width / this.resolution + 'px'; this.view.style.height = this.height / this.resolution + 'px'; @@ -429,18 +427,18 @@ * * @param texture {BaseTexture|Texture} the texture to update */ -WebGLRenderer.prototype.updateTexture = function (texture) +WebGLRenderer.prototype.updateTexture = function (texture) { texture = texture.baseTexture || texture; - if (!texture.hasLoaded) + if (!texture.hasLoaded) { return; } var gl = this.gl; - if (!texture._glTextures[gl.id]) + if (!texture._glTextures[gl.id]) { texture._glTextures[gl.id] = gl.createTexture(); texture.on('update', this._boundUpdateTexture); @@ -455,22 +453,22 @@ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, texture.scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); - if (texture.mipmap && utils.isPowerOfTwo(texture.width, texture.height)) + if (texture.mipmap && utils.isPowerOfTwo(texture.width, texture.height)) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); gl.generateMipmap(gl.TEXTURE_2D); } - else + else { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); } - if (!texture._powerOf2) + if (!texture._powerOf2) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); } - else + else { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); @@ -479,16 +477,16 @@ return texture._glTextures[gl.id]; }; -WebGLRenderer.prototype.destroyTexture = function (texture) +WebGLRenderer.prototype.destroyTexture = function (texture) { texture = texture.baseTexture || texture; - if (!texture.hasLoaded) + if (!texture.hasLoaded) { return; } - if (texture._glTextures[this.gl.id]) + if (texture._glTextures[this.gl.id]) { this.gl.deleteTexture(texture._glTextures[this.gl.id]); } @@ -500,7 +498,7 @@ * @param event {Event} * @private */ -WebGLRenderer.prototype.handleContextLost = function (event) +WebGLRenderer.prototype.handleContextLost = function (event) { event.preventDefault(); }; @@ -511,12 +509,12 @@ * @param event {Event} * @private */ -WebGLRenderer.prototype.handleContextRestored = function () +WebGLRenderer.prototype.handleContextRestored = function () { this._initContext(); // empty all the ol gl textures as they are useless now - for (var key in utils.TextureCache) + for (var key in utils.TextureCache) { var texture = utils.TextureCache[key].baseTexture; texture._glTextures = []; @@ -528,9 +526,9 @@ * * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ -WebGLRenderer.prototype.destroy = function (removeView) +WebGLRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parent) + if (removeView && this.view.parent) { this.view.parent.removeChild(this.view); } @@ -541,10 +539,15 @@ // time to create the render managers! each one focuses on managine a state in webGL this.shaderManager.destroy(); - this.spriteRenderer.destroy(); this.maskManager.destroy(); this.filterManager.destroy(); + for (var o in this.objectRenderers) { + this.objectRenderers[o].destroy(); + this.objectRenderers[o] = null; + } + + this.objectRenderers = null; // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -574,7 +577,6 @@ this.drawCount = 0; this.shaderManager = null; - this.spriteRenderer = null; this.maskManager = null; this.filterManager = null; this.stencilManager = null; @@ -591,11 +593,11 @@ * * @private */ -WebGLRenderer.prototype._mapBlendModes = function () +WebGLRenderer.prototype._mapBlendModes = function () { var gl = this.gl; - if (!this.blendModes) + if (!this.blendModes) { this.blendModes = {}; @@ -620,3 +622,9 @@ }; WebGLRenderer.glContextId = 0; +WebGLRenderer._objectRenderers = {}; + +WebGLRenderer.registerObjectRenderer = function (name, ctor) { + WebGLRenderer._objectRenderers[name] = ctor; +}; + diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 0669e79..c2294f9 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,7 +500,7 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.spriteRenderer.flush(); + renderer.objectRenderers.sprite.flush(); renderer.filterManager.pushFilter(this._filterBlock); } diff --git a/src/core/display/Sprite.js b/src/core/display/Sprite.js deleted file mode 100644 index e4e9d01..0000000 --- a/src/core/display/Sprite.js +++ /dev/null @@ -1,461 +0,0 @@ -var math = require('../math'), - Texture = require('../textures/Texture'), - DisplayObjectContainer = require('./DisplayObjectContainer'), - CanvasTinter = require('../renderers/canvas/utils/CanvasTinter'), - utils = require('../utils'), - CONST = require('../const'); - -/** - * The Sprite object is the base for all textured objects that are rendered to the screen - * - * A sprite can be created directly from an image like this: - * - * ```js - * var sprite = new Sprite.fromImage('assets/image.png'); - * ``` - * - * @class Sprite - * @extends DisplayObjectContainer - * @namespace PIXI - * @param texture {Texture} The texture for this sprite - */ -function Sprite(texture) -{ - DisplayObjectContainer.call(this); - - /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting than anchor to 0.5,0.5 means the textures origin is centered - * Setting the anchor to 1,1 would mean the textures origin points will be the bottom right corner - * - * @member {Point} - */ - this.anchor = new math.Point(); - - /** - * The texture that the sprite is using - * - * @member {Texture} - * @private - */ - this._texture = null; - - /** - * The width of the sprite (this is initially set by the texture) - * - * @member {number} - * @private - */ - this._width = 0; - - /** - * The height of the sprite (this is initially set by the texture) - * - * @member {number} - * @private - */ - this._height = 0; - - /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the sprite. Set to CONST.blendModes.NORMAL to remove any blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. - * - * @member {AbstractFilter} - */ - this.shader = null; - - this.renderable = true; - - // call texture setter - this.texture = texture || Texture.EMPTY; -} - -Sprite.prototype.destroy = function (destroyTexture, destroyBaseTexture) -{ - DisplayObjectContainer.prototype.destroy.call(this); - - this.anchor = null; - - if (destroyTexture) - { - this._texture.destroy(destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// constructor -Sprite.prototype = Object.create(DisplayObjectContainer.prototype); -Sprite.prototype.constructor = Sprite; -module.exports = Sprite; - -Object.defineProperties(Sprite.prototype, { - /** - * The width of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - width: { - get: function () - { - return this.scale.x * this.texture.frame.width; - }, - set: function (value) - { - this.scale.x = value / this.texture.frame.width; - this._width = value; - } - }, - - /** - * The height of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - height: { - get: function () - { - return this.scale.y * this.texture.frame.height; - }, - set: function (value) - { - this.scale.y = value / this.texture.frame.height; - this._height = value; - } - }, - - /** - * The height of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - texture: { - get: function () - { - return this._texture; - }, - set: function (value) - { - if (this._texture === value) - { - return; - } - - this._texture = value; - this.cachedTint = 0xFFFFFF; - - if (value) - { - // wait for the texture to load - if (value.baseTexture.hasLoaded) - { - this._onTextureUpdate(); - } - else - { - value.once('update', this._onTextureUpdate.bind(this)); - } - } - } - }, -}); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = this._width / this.texture.frame.width; - } - - if (this._height) - { - this.scale.y = this._height / this.texture.frame.height; - } -}; - -Sprite.prototype._renderWebGL = function (renderer) -{ - - // this is where content gets renderd.. - // watch this space for a little render state manager.. - renderer.setObjectRenderer(renderer.spriteRenderer); - renderer.spriteRenderer.render(this); -}; - -/** - * Returns the bounds of the Sprite as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param matrix {Matrix} the transformation matrix of the sprite - * @return {Rectangle} the framing rectangle - */ -Sprite.prototype.getBounds = function (matrix) -{ - var width = this.texture.frame.width; - var height = this.texture.frame.height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = matrix || this.worldTransform ; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var minX, - maxX, - minY, - maxY; - - if(b === 0 && c === 0) - { - // scale may be negative! - if (a < 0) - { - a *= -1; - } - - if (d < 0) - { - d *= -1; - } - - // this means there is no rotation going on right? RIGHT? - // if thats the case then we can avoid checking the bound values! yay - minX = a * w1 + tx; - maxX = a * w0 + tx; - minY = d * h1 + ty; - maxY = d * h0 + ty; - } - else - { - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {CanvasRenderer} The renderer -*/ -Sprite.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || this.texture.crop.width <= 0 || this.texture.crop.height <= 0) - { - return; - } - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - renderer.context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - // Ignore null sources - if (this.texture.valid) - { - var resolution = this.texture.baseTexture.resolution / renderer.resolution; - - renderer.context.globalAlpha = this.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for this texture - if (renderer.smoothProperty && renderer.scaleMode !== this.texture.baseTexture.scaleMode) - { - renderer.scaleMode = this.texture.baseTexture.scaleMode; - renderer.context[renderer.smoothProperty] = (renderer.scaleMode === CONST.scaleModes.LINEAR); - } - - // If the texture is trimmed we offset by the trim x/y, otherwise we use the frame dimensions - var dx = (this.texture.trim ? this.texture.trim.x : 0) - (this.anchor.x * this.texture.trim.width); - var dy = (this.texture.trim ? this.texture.trim.y : 0) - (this.anchor.y * this.texture.trim.height); - - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - this.worldTransform.a, - this.worldTransform.b, - this.worldTransform.c, - this.worldTransform.d, - (this.worldTransform.tx * renderer.resolution) | 0, - (this.worldTransform.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - this.worldTransform.a, - this.worldTransform.b, - this.worldTransform.c, - this.worldTransform.d, - this.worldTransform.tx * renderer.resolution, - this.worldTransform.ty * renderer.resolution - ); - } - - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - // TODO clean up caching - how to clean up the caches? - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - - renderer.context.drawImage( - this.tintedTexture, - 0, - 0, - this.texture.crop.width, - this.texture.crop.height, - dx / resolution, - dy / resolution, - this.texture.crop.width / resolution, - this.texture.crop.height / resolution - ); - } - else - { - renderer.context.drawImage( - this.texture.baseTexture.source, - this.texture.crop.x, - this.texture.crop.y, - this.texture.crop.width, - this.texture.crop.height, - dx / resolution, - dy / resolution, - this.texture.crop.width / resolution, - this.texture.crop.height / resolution - ); - } - } - - for (var i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -// some helper functions.. - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {String} The frame Id of the texture in the cache - * @return {Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache' + this); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {String} The image url of the texture - * @return {Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/display/SpriteBatch.js b/src/core/display/SpriteBatch.js deleted file mode 100644 index c196fef..0000000 --- a/src/core/display/SpriteBatch.js +++ /dev/null @@ -1,183 +0,0 @@ -var DisplayObjectContainer = require('./DisplayObjectContainer'), - WebGLFastSpriteBatch = require('../renderers/webgl/utils/WebGLFastSpriteBatch'); - -/** - * The SpriteBatch class is a really fast version of the DisplayObjectContainer built solely for speed, - * so use when you need a lot of sprites or particles. The tradeoff of the SpriteBatch is that advanced - * functionality will not work. SpriteBatch implements only the basic object transform (position, scale, rotation). - * Any other functionality like tinting, masking, etc will not work on sprites in this batch. - * - * It's extremely easy to use : - * - * ```js - * var container = new SpriteBatch(); - * - * for(var i = 0; i < 100; ++i) - * { - * var sprite = new PIXI.Sprite.fromImage("myImage.png"); - * container.addChild(sprite); - * } - * ``` - * - * And here you have a hundred sprites that will be renderer at the speed of light. - * - * @class - * @namespace PIXI - */ - -//TODO RENAME to PARTICLE CONTAINER? -function SpriteBatch() -{ - DisplayObjectContainer.call(this); -} - -SpriteBatch.prototype = Object.create(DisplayObjectContainer.prototype); -SpriteBatch.prototype.constructor = SpriteBatch; -module.exports = SpriteBatch; - -/** - * Updates the object transform for rendering - * - * @private - */ -SpriteBatch.prototype.updateTransform = function () -{ - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.DisplayObjectContainer.prototype.updateTransform.call( this ); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} The webgl renderer - * @private - */ -SpriteBatch.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || !this.children.length) - { - return; - } - - renderer.spriteBatch.stop(); - - renderer.shaderManager.setShader(renderer.shaderManager.fastShader); - - renderer.fastSpriteBatch.begin(this); - renderer.fastSpriteBatch.render(this); - - renderer.spriteBatch.start(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} The canvas renderer - * @private - */ -SpriteBatch.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || !this.children.length) - { - return; - } - - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - var frame = child.texture.frame; - - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) - { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) - { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx, - transform.ty - ); - - isRotated = false; - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5) | 0, - ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5) | 0, - frame.width * child.scale.x, - frame.height * child.scale.y - ); - } - else - { - if (!isRotated) - { - isRotated = true; - } - - child.displayObjectUpdateTransform(); - - var childTransform = child.worldTransform; - - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx | 0, - childTransform.ty | 0 - ); - } - else - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx, - childTransform.ty - ); - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - ((child.anchor.x) * (-frame.width) + 0.5) | 0, - ((child.anchor.y) * (-frame.height) + 0.5) | 0, - frame.width, - frame.height - ); - } - } -}; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js index 70d28b1..baab0f8 100644 --- a/src/core/primitives/Graphics.js +++ b/src/core/primitives/Graphics.js @@ -738,17 +738,17 @@ return; } - + */ - + if (this.glDirty) { this.dirty = true; this.glDirty = false; } - renderer.setObjectRenderer(renderer.graphicsRenderer); - renderer.graphicsRenderer.render(this); + renderer.setObjectRenderer(renderer.objectRenderers.graphics); + renderer.objectRenderers.graphics.render(this); }; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js new file mode 100644 index 0000000..55dc7a5 --- /dev/null +++ b/src/core/primitives/GraphicsRenderer.js @@ -0,0 +1,874 @@ +var utils = require('../../../utils'), + math = require('../../../math'), + CONST = require('../../../const'), + ObjectRenderer = require('../renderers/webgl/utils/ObjectRenderer'), + WebGLRenderer = require('../renderers/webgl/WebGLRenderer'), + WebGLGraphicsData = require('./WebGLGraphicsData'); + +/** + * Renders the graphics object. + * + * @class + * @private + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this object renderer works for. + */ +function GraphicsRenderer(renderer) +{ + ObjectRenderer.call(this, renderer); + + this.graphicsDataPool = []; +} + +GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); +GraphicsRenderer.prototype.constructor = GraphicsRenderer; +module.exports = GraphicsRenderer; + +WebGLRenderer.registerObjectRenderer('graphics', GraphicsRenderer); + +/** + * Destroys this renderer. + * + */ +GraphicsRenderer.prototype.destroy = function () { + ObjectRenderer.prototype.destroy.call(this); + + this.graphicsDataPool = null; +}; + +/** + * Renders a graphics object. + * + * @param graphics {Graphics} The graphics object to render. + */ +GraphicsRenderer.prototype.render = function(graphics) +{ + var renderer = this.renderer; + var gl = renderer.gl; + + var projection = renderer.projection, + offset = renderer.offset, + shader = renderer.shaderManager.primitiveShader, + webGLData; + + if (graphics.dirty) + { + this.updateGraphics(graphics, gl); + } + + var webGL = graphics._webGL[gl.id]; + + // This could be speeded up for sure! + + renderer.blendModeManager.setBlendMode( graphics.blendMode ); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.stencilManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.stencilManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.primitiveShader; + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniform1f(shader.uniforms.flipY._location, 1); + + gl.uniform2f(shader.uniforms.projectionVector._location, projection.x, -projection.y); + gl.uniform2f(shader.uniforms.offsetVector._location, -offset.x, -offset.y); + + gl.uniform3fv(shader.uniforms.tint._location, utils.hex2rgb(graphics.tint)); + + gl.uniform1f(shader.uniforms.alpha._location, graphics.worldAlpha); + + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.attributes.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); + gl.vertexAttribPointer(shader.attributes.aColor, 4, gl.FLOAT, false,4 * 6, 2 * 4); + + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + } + } +} + +/** + * Updates the graphics object + * + * @private + * @param graphicsData {Graphics} The graphics object to update + * @param gl {WebGLContext} the current WebGL drawing context + */ +GraphicsRenderer.prototype.updateGraphics = function(graphics) +{ + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[gl.id]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[gl.id] = {lastIndex:0, data:[], gl:gl}; + } + + // flag the graphics as not dirty as we are about to update it... + graphics.dirty = false; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty) + { + graphics.clearDirty = false; + + // lop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + graphicsData.reset(); + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + if (data.type === CONST.SHAPES.POLY) + { + // need to add the points the the graphics object.. + data.points = data.shape.points.slice(); + if (data.shape.closed) + { + // close the poly if the value is true! + if (data.points[0] !== data.points[data.points.length-2] || data.points[1] !== data.points[data.points.length-1]) + { + data.points.push(data.points[0], data.points[1]); + } + } + + // MAKE SURE WE HAVE THE CORRECT TYPE.. + if (data.fill) + { + if (data.points.length >= 6) + { + if (data.points.length < 6 * 2) + { + webGLData = this.switchMode(webGL, 0); + + var canDrawUsingSimple = this.buildPoly(data, webGLData); + // console.log(canDrawUsingSimple); + + if (!canDrawUsingSimple) + { + // console.log("<>>>") + webGLData = this.switchMode(webGL, 1); + this.buildComplexPoly(data, webGLData); + } + + } + else + { + webGLData = this.switchMode(webGL, 1); + this.buildComplexPoly(data, webGLData); + } + } + } + + if (data.lineWidth > 0) + { + webGLData = this.switchMode(webGL, 0); + this.buildLine(data, webGLData); + } + } + else + { + webGLData = this.switchMode(webGL, 0); + + if (data.type === CONST.SHAPES.RECT) + { + this.buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + this.buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + this.buildRoundedRectangle(data, webGLData); + } + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } +} + +/** + * + * + * @private + * @param webGL {WebGLContext} + * @param type {number} + */ +GraphicsRenderer.prototype.switchMode = function (webGL, type) +{ + var webGLData; + + if (!webGL.data.length) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(webGL.gl); + webGLData.mode = type; + webGL.data.push(webGLData); + } + else + { + webGLData = webGL.data[webGL.data.length-1]; + + if (webGLData.mode !== type || type === 1) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(webGL.gl); + webGLData.mode = type; + webGL.data.push(webGLData); + } + } + + webGLData.dirty = true; + + return webGLData; +}; + +/** + * Builds a rectangle to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildRectangle = function (graphicsData, webGLData) +{ + // --- // + // need to convert points to a nice regular data + // + var rectData = graphicsData.shape; + var x = rectData.x; + var y = rectData.y; + var width = rectData.width; + var height = rectData.height; + + if (graphicsData.fill) + { + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var verts = webGLData.points; + var indices = webGLData.indices; + + var vertPos = verts.length/6; + + // start + verts.push(x, y); + verts.push(r, g, b, alpha); + + verts.push(x + width, y); + verts.push(r, g, b, alpha); + + verts.push(x , y + height); + verts.push(r, g, b, alpha); + + verts.push(x + width, y + height); + verts.push(r, g, b, alpha); + + // insert 2 dead triangles.. + indices.push(vertPos, vertPos, vertPos+1, vertPos+2, vertPos+3, vertPos+3); + } + + if (graphicsData.lineWidth) + { + var tempPoints = graphicsData.points; + + graphicsData.points = [x, y, + x + width, y, + x + width, y + height, + x, y + height, + x, y]; + + + this.buildLine(graphicsData, webGLData); + + graphicsData.points = tempPoints; + } +}; + +/** + * Builds a rounded rectangle to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildRoundedRectangle = function (graphicsData, webGLData) +{ + var rrectData = graphicsData.shape; + var x = rrectData.x; + var y = rrectData.y; + var width = rrectData.width; + var height = rrectData.height; + + var radius = rrectData.radius; + + var recPoints = []; + recPoints.push(x, y + radius); + recPoints = recPoints.concat(this.quadraticBezierCurve(x, y + height - radius, x, y + height, x + radius, y + height)); + recPoints = recPoints.concat(this.quadraticBezierCurve(x + width - radius, y + height, x + width, y + height, x + width, y + height - radius)); + recPoints = recPoints.concat(this.quadraticBezierCurve(x + width, y + radius, x + width, y, x + width - radius, y)); + recPoints = recPoints.concat(this.quadraticBezierCurve(x + radius, y, x, y, x, y + radius)); + + if (graphicsData.fill) + { + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var verts = webGLData.points; + var indices = webGLData.indices; + + var vecPos = verts.length/6; + + var triangles = utils.PolyK.Triangulate(recPoints); + + // + + var i = 0; + for (i = 0; i < triangles.length; i+=3) + { + indices.push(triangles[i] + vecPos); + indices.push(triangles[i] + vecPos); + indices.push(triangles[i+1] + vecPos); + indices.push(triangles[i+2] + vecPos); + indices.push(triangles[i+2] + vecPos); + } + + for (i = 0; i < recPoints.length; i++) + { + verts.push(recPoints[i], recPoints[++i], r, g, b, alpha); + } + } + + if (graphicsData.lineWidth) + { + var tempPoints = graphicsData.points; + + graphicsData.points = recPoints; + + this.buildLine(graphicsData, webGLData); + + graphicsData.points = tempPoints; + } +}; + +/** + * Calculate the points for a quadratic bezier curve. (helper function..) + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @private + * @param fromX {number} Origin point x + * @param fromY {number} Origin point x + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {number[]} + */ +GraphicsRenderer.prototype.quadraticBezierCurve = function (fromX, fromY, cpX, cpY, toX, toY) +{ + + var xa, + ya, + xb, + yb, + x, + y, + n = 20, + points = []; + + function getPt(n1 , n2, perc) { + var diff = n2 - n1; + + return n1 + ( diff * perc ); + } + + var j = 0; + for (var i = 0; i <= n; i++ ) { + j = i / n; + + // The Green Line + xa = getPt( fromX , cpX , j ); + ya = getPt( fromY , cpY , j ); + xb = getPt( cpX , toX , j ); + yb = getPt( cpY , toY , j ); + + // The Black Dot + x = getPt( xa , xb , j ); + y = getPt( ya , yb , j ); + + points.push(x, y); + } + return points; +}; + +/** + * Builds a circle to draw + * + * @private + * @param graphicsData {Graphics} The graphics object to draw + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildCircle = function (graphicsData, webGLData) +{ + // need to convert points to a nice regular data + var circleData = graphicsData.shape; + var x = circleData.x; + var y = circleData.y; + var width; + var height; + + // TODO - bit hacky?? + if (graphicsData.type === CONST.SHAPES.CIRC) + { + width = circleData.radius; + height = circleData.radius; + } + else + { + width = circleData.width; + height = circleData.height; + } + + var totalSegs = 40; + var seg = (Math.PI * 2) / totalSegs ; + + var i = 0; + + if (graphicsData.fill) + { + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var verts = webGLData.points; + var indices = webGLData.indices; + + var vecPos = verts.length/6; + + indices.push(vecPos); + + for (i = 0; i < totalSegs + 1 ; i++) + { + verts.push(x,y, r, g, b, alpha); + + verts.push(x + Math.sin(seg * i) * width, + y + Math.cos(seg * i) * height, + r, g, b, alpha); + + indices.push(vecPos++, vecPos++); + } + + indices.push(vecPos-1); + } + + if (graphicsData.lineWidth) + { + var tempPoints = graphicsData.points; + + graphicsData.points = []; + + for (i = 0; i < totalSegs + 1; i++) + { + graphicsData.points.push(x + Math.sin(seg * i) * width, + y + Math.cos(seg * i) * height); + } + + this.buildLine(graphicsData, webGLData); + + graphicsData.points = tempPoints; + } +}; + +/** + * Builds a line to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildLine = function (graphicsData, webGLData) +{ + // TODO OPTIMISE! + var i = 0; + var points = graphicsData.points; + + if (points.length === 0) + { + return; + } + + // if the line width is an odd number add 0.5 to align to a whole pixel + if (graphicsData.lineWidth%2) + { + for (i = 0; i < points.length; i++) + { + points[i] += 0.5; + } + } + + // get first and last point.. figure out the middle! + var firstPoint = new math.Point(points[0], points[1]); + var lastPoint = new math.Point(points[points.length - 2], points[points.length - 1]); + + // if the first point is the last point - gonna have issues :) + if (firstPoint.x === lastPoint.x && firstPoint.y === lastPoint.y) + { + // need to clone as we are going to slightly modify the shape.. + points = points.slice(); + + points.pop(); + points.pop(); + + lastPoint = new math.Point(points[points.length - 2], points[points.length - 1]); + + var midPointX = lastPoint.x + (firstPoint.x - lastPoint.x) *0.5; + var midPointY = lastPoint.y + (firstPoint.y - lastPoint.y) *0.5; + + points.unshift(midPointX, midPointY); + points.push(midPointX, midPointY); + } + + var verts = webGLData.points; + var indices = webGLData.indices; + var length = points.length / 2; + var indexCount = points.length; + var indexStart = verts.length/6; + + // DRAW the Line + var width = graphicsData.lineWidth / 2; + + // sort color + var color = utils.hex2rgb(graphicsData.lineColor); + var alpha = graphicsData.lineAlpha; + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var px, py, p1x, p1y, p2x, p2y, p3x, p3y; + var perpx, perpy, perp2x, perp2y, perp3x, perp3y; + var a1, b1, c1, a2, b2, c2; + var denom, pdist, dist; + + p1x = points[0]; + p1y = points[1]; + + p2x = points[2]; + p2y = points[3]; + + perpx = -(p1y - p2y); + perpy = p1x - p2x; + + dist = Math.sqrt(perpx*perpx + perpy*perpy); + + perpx /= dist; + perpy /= dist; + perpx *= width; + perpy *= width; + + // start + verts.push(p1x - perpx , p1y - perpy, + r, g, b, alpha); + + verts.push(p1x + perpx , p1y + perpy, + r, g, b, alpha); + + for (i = 1; i < length-1; i++) + { + p1x = points[(i-1)*2]; + p1y = points[(i-1)*2 + 1]; + + p2x = points[(i)*2]; + p2y = points[(i)*2 + 1]; + + p3x = points[(i+1)*2]; + p3y = points[(i+1)*2 + 1]; + + perpx = -(p1y - p2y); + perpy = p1x - p2x; + + dist = Math.sqrt(perpx*perpx + perpy*perpy); + perpx /= dist; + perpy /= dist; + perpx *= width; + perpy *= width; + + perp2x = -(p2y - p3y); + perp2y = p2x - p3x; + + dist = Math.sqrt(perp2x*perp2x + perp2y*perp2y); + perp2x /= dist; + perp2y /= dist; + perp2x *= width; + perp2y *= width; + + a1 = (-perpy + p1y) - (-perpy + p2y); + b1 = (-perpx + p2x) - (-perpx + p1x); + c1 = (-perpx + p1x) * (-perpy + p2y) - (-perpx + p2x) * (-perpy + p1y); + a2 = (-perp2y + p3y) - (-perp2y + p2y); + b2 = (-perp2x + p2x) - (-perp2x + p3x); + c2 = (-perp2x + p3x) * (-perp2y + p2y) - (-perp2x + p2x) * (-perp2y + p3y); + + denom = a1*b2 - a2*b1; + + if (Math.abs(denom) < 0.1 ) + { + + denom+=10.1; + verts.push(p2x - perpx , p2y - perpy, + r, g, b, alpha); + + verts.push(p2x + perpx , p2y + perpy, + r, g, b, alpha); + + continue; + } + + px = (b1*c2 - b2*c1)/denom; + py = (a2*c1 - a1*c2)/denom; + + + pdist = (px -p2x) * (px -p2x) + (py -p2y) + (py -p2y); + + + if (pdist > 140 * 140) + { + perp3x = perpx - perp2x; + perp3y = perpy - perp2y; + + dist = Math.sqrt(perp3x*perp3x + perp3y*perp3y); + perp3x /= dist; + perp3y /= dist; + perp3x *= width; + perp3y *= width; + + verts.push(p2x - perp3x, p2y -perp3y); + verts.push(r, g, b, alpha); + + verts.push(p2x + perp3x, p2y +perp3y); + verts.push(r, g, b, alpha); + + verts.push(p2x - perp3x, p2y -perp3y); + verts.push(r, g, b, alpha); + + indexCount++; + } + else + { + + verts.push(px , py); + verts.push(r, g, b, alpha); + + verts.push(p2x - (px-p2x), p2y - (py - p2y)); + verts.push(r, g, b, alpha); + } + } + + p1x = points[(length-2)*2]; + p1y = points[(length-2)*2 + 1]; + + p2x = points[(length-1)*2]; + p2y = points[(length-1)*2 + 1]; + + perpx = -(p1y - p2y); + perpy = p1x - p2x; + + dist = Math.sqrt(perpx*perpx + perpy*perpy); + perpx /= dist; + perpy /= dist; + perpx *= width; + perpy *= width; + + verts.push(p2x - perpx , p2y - perpy); + verts.push(r, g, b, alpha); + + verts.push(p2x + perpx , p2y + perpy); + verts.push(r, g, b, alpha); + + indices.push(indexStart); + + for (i = 0; i < indexCount; i++) + { + indices.push(indexStart++); + } + + indices.push(indexStart-1); +}; + +/** + * Builds a complex polygon to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildComplexPoly = function (graphicsData, webGLData) +{ + //TODO - no need to copy this as it gets turned into a FLoat32Array anyways.. + var points = graphicsData.points.slice(); + + if (points.length < 6) + { + return; + } + + // get first and last point.. figure out the middle! + var indices = webGLData.indices; + webGLData.points = points; + webGLData.alpha = graphicsData.fillAlpha; + webGLData.color = utils.hex2rgb(graphicsData.fillColor); + + // calclate the bounds.. + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + var x,y; + + // get size.. + for (var i = 0; i < points.length; i+=2) + { + x = points[i]; + y = points[i+1]; + + minX = x < minX ? x : minX; + maxX = x > maxX ? x : maxX; + + minY = y < minY ? y : minY; + maxY = y > maxY ? y : maxY; + } + + // add a quad to the end cos there is no point making another buffer! + points.push(minX, minY, + maxX, minY, + maxX, maxY, + minX, maxY); + + // push a quad onto the end.. + + //TODO - this aint needed! + var length = points.length / 2; + for (i = 0; i < length; i++) + { + indices.push( i ); + } + +}; + +/** + * Builds a polygon to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildPoly = function (graphicsData, webGLData) +{ + var points = graphicsData.points; + + if (points.length < 6) + { + return; + } + + // get first and last point.. figure out the middle! + var verts = webGLData.points; + var indices = webGLData.indices; + + var length = points.length / 2; + + // sort color + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var triangles = utils.PolyK.Triangulate(points); + + if (!triangles) { + return false; + } + + var vertPos = verts.length / 6; + + var i = 0; + + for (i = 0; i < triangles.length; i+=3) + { + indices.push(triangles[i] + vertPos); + indices.push(triangles[i] + vertPos); + indices.push(triangles[i+1] + vertPos); + indices.push(triangles[i+2] +vertPos); + indices.push(triangles[i+2] + vertPos); + } + + for (i = 0; i < length; i++) + { + verts.push(points[i * 2], points[i * 2 + 1], + r, g, b, alpha); + } + + return true; +}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 547bdc7..b0189c5 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,6 +1,4 @@ -var SpriteRenderer = require('./utils/SpriteRenderer'), - GraphicsRenderer = require('./utils/GraphicsRenderer'), - WebGLFastSpriteBatch = require('./utils/WebGLFastSpriteBatch'), +var WebGLFastSpriteBatch = require('./utils/WebGLFastSpriteBatch'), WebGLShaderManager = require('./managers/WebGLShaderManager'), WebGLMaskManager = require('./managers/WebGLMaskManager'), WebGLFilterManager = require('./managers/WebGLFilterManager'), @@ -28,21 +26,21 @@ * @param [options.preserveDrawingBuffer=false] {boolean} enables drawing buffer preservation, enable this if you need to call toDataUrl on the webgl context * @param [options.resolution=1] {number} the resolution of the renderer retina would be 2 */ -function WebGLRenderer(width, height, options) +function WebGLRenderer(width, height, options) { utils.sayHello('webGL'); - if (options) + if (options) { - for (var i in CONST.defaultRenderOptions) + for (var i in CONST.defaultRenderOptions) { - if (typeof options[i] === 'undefined') + if (typeof options[i] === 'undefined') { options[i] = CONST.defaultRenderOptions[i]; } } } - else + else { options = CONST.defaultRenderOptions; } @@ -191,14 +189,6 @@ /** * Manages the rendering of sprites - * @member {SpriteRenderer} - */ - this.spriteRenderer = new SpriteRenderer(this); - - this.graphicsRenderer = new GraphicsRenderer(this); - - /** - * Manages the rendering of sprites * @member {WebGLFastSpriteBatch} */ this.fastSpriteBatch = new WebGLFastSpriteBatch(this); @@ -245,8 +235,17 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - this.currentRenderer = this.spriteRenderer; + /** + * Manages the renderer of specific objects. + * + * @member {object} + */ + this.objectRenderers = {}; + // create an instance of each registered object renderer + for (var o in WebGLRenderer._objectRenderers) { + this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); + } } // constructor @@ -255,21 +254,20 @@ utils.eventTarget.mixin(WebGLRenderer.prototype); -Object.defineProperties(WebGLRenderer.prototype, -{ +Object.defineProperties(WebGLRenderer.prototype, { /** * The background color to fill if not transparent * * @member {number} * @memberof WebGLRenderer# */ - backgroundColor: + backgroundColor: { - get: function () + get: function () { return this._backgroundColor; }, - set: function (val) + set: function (val) { this._backgroundColor = val; utils.hex2rgb(val, this._backgroundColorRgb); @@ -281,12 +279,12 @@ * * @private */ -WebGLRenderer.prototype._initContext = function () +WebGLRenderer.prototype._initContext = function () { var gl = this.view.getContext('webgl', this._contextOptions) || this.view.getContext('experimental-webgl', this._contextOptions); this.gl = gl; - if (!gl) + if (!gl) { // fail, not able to get a context throw new Error('This browser does not support webGL. Try using the canvas renderer'); @@ -312,10 +310,10 @@ * * @param object {DisplayObject} the object to be rendered */ -WebGLRenderer.prototype.render = function (object) +WebGLRenderer.prototype.render = function (object) { // no point rendering if our context has been blown up! - if (this.gl.isContextLost()) + if (this.gl.isContextLost()) { return; } @@ -336,13 +334,13 @@ // make sure we are bound to the main frame buffer gl.bindFramebuffer(gl.FRAMEBUFFER, null); - if (this.clearBeforeRender) + if (this.clearBeforeRender) { - if (this.transparent) + if (this.transparent) { gl.clearColor(0, 0, 0, 0); } - else + else { gl.clearColor(this._backgroundColorRgb[0], this._backgroundColorRgb[1], this._backgroundColorRgb[2], 1); } @@ -360,7 +358,7 @@ * @param projection {Point} The projection * @param buffer {Array} a standard WebGL buffer */ -WebGLRenderer.prototype.renderDisplayObject = function (displayObject, projection, buffer) +WebGLRenderer.prototype.renderDisplayObject = function (displayObject, projection, buffer) { this.blendModeManager.setBlendMode(CONST.blendModes.NORMAL); @@ -386,9 +384,9 @@ this.currentRenderer.flush(); }; -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) +WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) { - if(this.currentRenderer === objectRenderer) + if (this.currentRenderer === objectRenderer) { return; } @@ -396,7 +394,7 @@ this.currentRenderer.stop(); this.currentRenderer = objectRenderer; this.currentRenderer.start(); -} +}; /** * Resizes the webGL view to the specified width and height. @@ -404,7 +402,7 @@ * @param width {number} the new width of the webGL view * @param height {number} the new height of the webGL view */ -WebGLRenderer.prototype.resize = function (width, height) +WebGLRenderer.prototype.resize = function (width, height) { this.width = width * this.resolution; this.height = height * this.resolution; @@ -412,7 +410,7 @@ this.view.width = this.width; this.view.height = this.height; - if (this.autoResize) + if (this.autoResize) { this.view.style.width = this.width / this.resolution + 'px'; this.view.style.height = this.height / this.resolution + 'px'; @@ -429,18 +427,18 @@ * * @param texture {BaseTexture|Texture} the texture to update */ -WebGLRenderer.prototype.updateTexture = function (texture) +WebGLRenderer.prototype.updateTexture = function (texture) { texture = texture.baseTexture || texture; - if (!texture.hasLoaded) + if (!texture.hasLoaded) { return; } var gl = this.gl; - if (!texture._glTextures[gl.id]) + if (!texture._glTextures[gl.id]) { texture._glTextures[gl.id] = gl.createTexture(); texture.on('update', this._boundUpdateTexture); @@ -455,22 +453,22 @@ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, texture.scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); - if (texture.mipmap && utils.isPowerOfTwo(texture.width, texture.height)) + if (texture.mipmap && utils.isPowerOfTwo(texture.width, texture.height)) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); gl.generateMipmap(gl.TEXTURE_2D); } - else + else { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); } - if (!texture._powerOf2) + if (!texture._powerOf2) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); } - else + else { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); @@ -479,16 +477,16 @@ return texture._glTextures[gl.id]; }; -WebGLRenderer.prototype.destroyTexture = function (texture) +WebGLRenderer.prototype.destroyTexture = function (texture) { texture = texture.baseTexture || texture; - if (!texture.hasLoaded) + if (!texture.hasLoaded) { return; } - if (texture._glTextures[this.gl.id]) + if (texture._glTextures[this.gl.id]) { this.gl.deleteTexture(texture._glTextures[this.gl.id]); } @@ -500,7 +498,7 @@ * @param event {Event} * @private */ -WebGLRenderer.prototype.handleContextLost = function (event) +WebGLRenderer.prototype.handleContextLost = function (event) { event.preventDefault(); }; @@ -511,12 +509,12 @@ * @param event {Event} * @private */ -WebGLRenderer.prototype.handleContextRestored = function () +WebGLRenderer.prototype.handleContextRestored = function () { this._initContext(); // empty all the ol gl textures as they are useless now - for (var key in utils.TextureCache) + for (var key in utils.TextureCache) { var texture = utils.TextureCache[key].baseTexture; texture._glTextures = []; @@ -528,9 +526,9 @@ * * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ -WebGLRenderer.prototype.destroy = function (removeView) +WebGLRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parent) + if (removeView && this.view.parent) { this.view.parent.removeChild(this.view); } @@ -541,10 +539,15 @@ // time to create the render managers! each one focuses on managine a state in webGL this.shaderManager.destroy(); - this.spriteRenderer.destroy(); this.maskManager.destroy(); this.filterManager.destroy(); + for (var o in this.objectRenderers) { + this.objectRenderers[o].destroy(); + this.objectRenderers[o] = null; + } + + this.objectRenderers = null; // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -574,7 +577,6 @@ this.drawCount = 0; this.shaderManager = null; - this.spriteRenderer = null; this.maskManager = null; this.filterManager = null; this.stencilManager = null; @@ -591,11 +593,11 @@ * * @private */ -WebGLRenderer.prototype._mapBlendModes = function () +WebGLRenderer.prototype._mapBlendModes = function () { var gl = this.gl; - if (!this.blendModes) + if (!this.blendModes) { this.blendModes = {}; @@ -620,3 +622,9 @@ }; WebGLRenderer.glContextId = 0; +WebGLRenderer._objectRenderers = {}; + +WebGLRenderer.registerObjectRenderer = function (name, ctor) { + WebGLRenderer._objectRenderers[name] = ctor; +}; + diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js index 773cc5d..e81ca56 100644 --- a/src/core/renderers/webgl/managers/WebGLMaskManager.js +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -20,11 +20,11 @@ */ WebGLMaskManager.prototype.pushMask = function (maskData) { - this.renderer.setObjectRenderer( this.renderer.graphicsRenderer ); + this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); if (maskData.dirty) { - this.renderer.graphicsRenderer.updateGraphics(maskData, this.renderer.gl); + this.renderer.objectRenderers.graphics.updateGraphics(maskData, this.renderer.gl); } if (!maskData._webGL[this.renderer.gl.id].data.length) @@ -42,7 +42,7 @@ */ WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.setObjectRenderer( this.renderer.graphicsRenderer ); - + this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.stencilManager.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); }; diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 0669e79..c2294f9 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,7 +500,7 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.spriteRenderer.flush(); + renderer.objectRenderers.sprite.flush(); renderer.filterManager.pushFilter(this._filterBlock); } diff --git a/src/core/display/Sprite.js b/src/core/display/Sprite.js deleted file mode 100644 index e4e9d01..0000000 --- a/src/core/display/Sprite.js +++ /dev/null @@ -1,461 +0,0 @@ -var math = require('../math'), - Texture = require('../textures/Texture'), - DisplayObjectContainer = require('./DisplayObjectContainer'), - CanvasTinter = require('../renderers/canvas/utils/CanvasTinter'), - utils = require('../utils'), - CONST = require('../const'); - -/** - * The Sprite object is the base for all textured objects that are rendered to the screen - * - * A sprite can be created directly from an image like this: - * - * ```js - * var sprite = new Sprite.fromImage('assets/image.png'); - * ``` - * - * @class Sprite - * @extends DisplayObjectContainer - * @namespace PIXI - * @param texture {Texture} The texture for this sprite - */ -function Sprite(texture) -{ - DisplayObjectContainer.call(this); - - /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting than anchor to 0.5,0.5 means the textures origin is centered - * Setting the anchor to 1,1 would mean the textures origin points will be the bottom right corner - * - * @member {Point} - */ - this.anchor = new math.Point(); - - /** - * The texture that the sprite is using - * - * @member {Texture} - * @private - */ - this._texture = null; - - /** - * The width of the sprite (this is initially set by the texture) - * - * @member {number} - * @private - */ - this._width = 0; - - /** - * The height of the sprite (this is initially set by the texture) - * - * @member {number} - * @private - */ - this._height = 0; - - /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the sprite. Set to CONST.blendModes.NORMAL to remove any blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. - * - * @member {AbstractFilter} - */ - this.shader = null; - - this.renderable = true; - - // call texture setter - this.texture = texture || Texture.EMPTY; -} - -Sprite.prototype.destroy = function (destroyTexture, destroyBaseTexture) -{ - DisplayObjectContainer.prototype.destroy.call(this); - - this.anchor = null; - - if (destroyTexture) - { - this._texture.destroy(destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// constructor -Sprite.prototype = Object.create(DisplayObjectContainer.prototype); -Sprite.prototype.constructor = Sprite; -module.exports = Sprite; - -Object.defineProperties(Sprite.prototype, { - /** - * The width of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - width: { - get: function () - { - return this.scale.x * this.texture.frame.width; - }, - set: function (value) - { - this.scale.x = value / this.texture.frame.width; - this._width = value; - } - }, - - /** - * The height of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - height: { - get: function () - { - return this.scale.y * this.texture.frame.height; - }, - set: function (value) - { - this.scale.y = value / this.texture.frame.height; - this._height = value; - } - }, - - /** - * The height of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - texture: { - get: function () - { - return this._texture; - }, - set: function (value) - { - if (this._texture === value) - { - return; - } - - this._texture = value; - this.cachedTint = 0xFFFFFF; - - if (value) - { - // wait for the texture to load - if (value.baseTexture.hasLoaded) - { - this._onTextureUpdate(); - } - else - { - value.once('update', this._onTextureUpdate.bind(this)); - } - } - } - }, -}); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = this._width / this.texture.frame.width; - } - - if (this._height) - { - this.scale.y = this._height / this.texture.frame.height; - } -}; - -Sprite.prototype._renderWebGL = function (renderer) -{ - - // this is where content gets renderd.. - // watch this space for a little render state manager.. - renderer.setObjectRenderer(renderer.spriteRenderer); - renderer.spriteRenderer.render(this); -}; - -/** - * Returns the bounds of the Sprite as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param matrix {Matrix} the transformation matrix of the sprite - * @return {Rectangle} the framing rectangle - */ -Sprite.prototype.getBounds = function (matrix) -{ - var width = this.texture.frame.width; - var height = this.texture.frame.height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = matrix || this.worldTransform ; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var minX, - maxX, - minY, - maxY; - - if(b === 0 && c === 0) - { - // scale may be negative! - if (a < 0) - { - a *= -1; - } - - if (d < 0) - { - d *= -1; - } - - // this means there is no rotation going on right? RIGHT? - // if thats the case then we can avoid checking the bound values! yay - minX = a * w1 + tx; - maxX = a * w0 + tx; - minY = d * h1 + ty; - maxY = d * h0 + ty; - } - else - { - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {CanvasRenderer} The renderer -*/ -Sprite.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || this.texture.crop.width <= 0 || this.texture.crop.height <= 0) - { - return; - } - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - renderer.context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - // Ignore null sources - if (this.texture.valid) - { - var resolution = this.texture.baseTexture.resolution / renderer.resolution; - - renderer.context.globalAlpha = this.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for this texture - if (renderer.smoothProperty && renderer.scaleMode !== this.texture.baseTexture.scaleMode) - { - renderer.scaleMode = this.texture.baseTexture.scaleMode; - renderer.context[renderer.smoothProperty] = (renderer.scaleMode === CONST.scaleModes.LINEAR); - } - - // If the texture is trimmed we offset by the trim x/y, otherwise we use the frame dimensions - var dx = (this.texture.trim ? this.texture.trim.x : 0) - (this.anchor.x * this.texture.trim.width); - var dy = (this.texture.trim ? this.texture.trim.y : 0) - (this.anchor.y * this.texture.trim.height); - - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - this.worldTransform.a, - this.worldTransform.b, - this.worldTransform.c, - this.worldTransform.d, - (this.worldTransform.tx * renderer.resolution) | 0, - (this.worldTransform.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - this.worldTransform.a, - this.worldTransform.b, - this.worldTransform.c, - this.worldTransform.d, - this.worldTransform.tx * renderer.resolution, - this.worldTransform.ty * renderer.resolution - ); - } - - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - // TODO clean up caching - how to clean up the caches? - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - - renderer.context.drawImage( - this.tintedTexture, - 0, - 0, - this.texture.crop.width, - this.texture.crop.height, - dx / resolution, - dy / resolution, - this.texture.crop.width / resolution, - this.texture.crop.height / resolution - ); - } - else - { - renderer.context.drawImage( - this.texture.baseTexture.source, - this.texture.crop.x, - this.texture.crop.y, - this.texture.crop.width, - this.texture.crop.height, - dx / resolution, - dy / resolution, - this.texture.crop.width / resolution, - this.texture.crop.height / resolution - ); - } - } - - for (var i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -// some helper functions.. - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {String} The frame Id of the texture in the cache - * @return {Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache' + this); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {String} The image url of the texture - * @return {Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/display/SpriteBatch.js b/src/core/display/SpriteBatch.js deleted file mode 100644 index c196fef..0000000 --- a/src/core/display/SpriteBatch.js +++ /dev/null @@ -1,183 +0,0 @@ -var DisplayObjectContainer = require('./DisplayObjectContainer'), - WebGLFastSpriteBatch = require('../renderers/webgl/utils/WebGLFastSpriteBatch'); - -/** - * The SpriteBatch class is a really fast version of the DisplayObjectContainer built solely for speed, - * so use when you need a lot of sprites or particles. The tradeoff of the SpriteBatch is that advanced - * functionality will not work. SpriteBatch implements only the basic object transform (position, scale, rotation). - * Any other functionality like tinting, masking, etc will not work on sprites in this batch. - * - * It's extremely easy to use : - * - * ```js - * var container = new SpriteBatch(); - * - * for(var i = 0; i < 100; ++i) - * { - * var sprite = new PIXI.Sprite.fromImage("myImage.png"); - * container.addChild(sprite); - * } - * ``` - * - * And here you have a hundred sprites that will be renderer at the speed of light. - * - * @class - * @namespace PIXI - */ - -//TODO RENAME to PARTICLE CONTAINER? -function SpriteBatch() -{ - DisplayObjectContainer.call(this); -} - -SpriteBatch.prototype = Object.create(DisplayObjectContainer.prototype); -SpriteBatch.prototype.constructor = SpriteBatch; -module.exports = SpriteBatch; - -/** - * Updates the object transform for rendering - * - * @private - */ -SpriteBatch.prototype.updateTransform = function () -{ - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.DisplayObjectContainer.prototype.updateTransform.call( this ); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} The webgl renderer - * @private - */ -SpriteBatch.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || !this.children.length) - { - return; - } - - renderer.spriteBatch.stop(); - - renderer.shaderManager.setShader(renderer.shaderManager.fastShader); - - renderer.fastSpriteBatch.begin(this); - renderer.fastSpriteBatch.render(this); - - renderer.spriteBatch.start(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} The canvas renderer - * @private - */ -SpriteBatch.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || !this.children.length) - { - return; - } - - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - var frame = child.texture.frame; - - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) - { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) - { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx, - transform.ty - ); - - isRotated = false; - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5) | 0, - ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5) | 0, - frame.width * child.scale.x, - frame.height * child.scale.y - ); - } - else - { - if (!isRotated) - { - isRotated = true; - } - - child.displayObjectUpdateTransform(); - - var childTransform = child.worldTransform; - - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx | 0, - childTransform.ty | 0 - ); - } - else - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx, - childTransform.ty - ); - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - ((child.anchor.x) * (-frame.width) + 0.5) | 0, - ((child.anchor.y) * (-frame.height) + 0.5) | 0, - frame.width, - frame.height - ); - } - } -}; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js index 70d28b1..baab0f8 100644 --- a/src/core/primitives/Graphics.js +++ b/src/core/primitives/Graphics.js @@ -738,17 +738,17 @@ return; } - + */ - + if (this.glDirty) { this.dirty = true; this.glDirty = false; } - renderer.setObjectRenderer(renderer.graphicsRenderer); - renderer.graphicsRenderer.render(this); + renderer.setObjectRenderer(renderer.objectRenderers.graphics); + renderer.objectRenderers.graphics.render(this); }; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js new file mode 100644 index 0000000..55dc7a5 --- /dev/null +++ b/src/core/primitives/GraphicsRenderer.js @@ -0,0 +1,874 @@ +var utils = require('../../../utils'), + math = require('../../../math'), + CONST = require('../../../const'), + ObjectRenderer = require('../renderers/webgl/utils/ObjectRenderer'), + WebGLRenderer = require('../renderers/webgl/WebGLRenderer'), + WebGLGraphicsData = require('./WebGLGraphicsData'); + +/** + * Renders the graphics object. + * + * @class + * @private + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this object renderer works for. + */ +function GraphicsRenderer(renderer) +{ + ObjectRenderer.call(this, renderer); + + this.graphicsDataPool = []; +} + +GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); +GraphicsRenderer.prototype.constructor = GraphicsRenderer; +module.exports = GraphicsRenderer; + +WebGLRenderer.registerObjectRenderer('graphics', GraphicsRenderer); + +/** + * Destroys this renderer. + * + */ +GraphicsRenderer.prototype.destroy = function () { + ObjectRenderer.prototype.destroy.call(this); + + this.graphicsDataPool = null; +}; + +/** + * Renders a graphics object. + * + * @param graphics {Graphics} The graphics object to render. + */ +GraphicsRenderer.prototype.render = function(graphics) +{ + var renderer = this.renderer; + var gl = renderer.gl; + + var projection = renderer.projection, + offset = renderer.offset, + shader = renderer.shaderManager.primitiveShader, + webGLData; + + if (graphics.dirty) + { + this.updateGraphics(graphics, gl); + } + + var webGL = graphics._webGL[gl.id]; + + // This could be speeded up for sure! + + renderer.blendModeManager.setBlendMode( graphics.blendMode ); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.stencilManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.stencilManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.primitiveShader; + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniform1f(shader.uniforms.flipY._location, 1); + + gl.uniform2f(shader.uniforms.projectionVector._location, projection.x, -projection.y); + gl.uniform2f(shader.uniforms.offsetVector._location, -offset.x, -offset.y); + + gl.uniform3fv(shader.uniforms.tint._location, utils.hex2rgb(graphics.tint)); + + gl.uniform1f(shader.uniforms.alpha._location, graphics.worldAlpha); + + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.attributes.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); + gl.vertexAttribPointer(shader.attributes.aColor, 4, gl.FLOAT, false,4 * 6, 2 * 4); + + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + } + } +} + +/** + * Updates the graphics object + * + * @private + * @param graphicsData {Graphics} The graphics object to update + * @param gl {WebGLContext} the current WebGL drawing context + */ +GraphicsRenderer.prototype.updateGraphics = function(graphics) +{ + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[gl.id]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[gl.id] = {lastIndex:0, data:[], gl:gl}; + } + + // flag the graphics as not dirty as we are about to update it... + graphics.dirty = false; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty) + { + graphics.clearDirty = false; + + // lop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + graphicsData.reset(); + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + if (data.type === CONST.SHAPES.POLY) + { + // need to add the points the the graphics object.. + data.points = data.shape.points.slice(); + if (data.shape.closed) + { + // close the poly if the value is true! + if (data.points[0] !== data.points[data.points.length-2] || data.points[1] !== data.points[data.points.length-1]) + { + data.points.push(data.points[0], data.points[1]); + } + } + + // MAKE SURE WE HAVE THE CORRECT TYPE.. + if (data.fill) + { + if (data.points.length >= 6) + { + if (data.points.length < 6 * 2) + { + webGLData = this.switchMode(webGL, 0); + + var canDrawUsingSimple = this.buildPoly(data, webGLData); + // console.log(canDrawUsingSimple); + + if (!canDrawUsingSimple) + { + // console.log("<>>>") + webGLData = this.switchMode(webGL, 1); + this.buildComplexPoly(data, webGLData); + } + + } + else + { + webGLData = this.switchMode(webGL, 1); + this.buildComplexPoly(data, webGLData); + } + } + } + + if (data.lineWidth > 0) + { + webGLData = this.switchMode(webGL, 0); + this.buildLine(data, webGLData); + } + } + else + { + webGLData = this.switchMode(webGL, 0); + + if (data.type === CONST.SHAPES.RECT) + { + this.buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + this.buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + this.buildRoundedRectangle(data, webGLData); + } + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } +} + +/** + * + * + * @private + * @param webGL {WebGLContext} + * @param type {number} + */ +GraphicsRenderer.prototype.switchMode = function (webGL, type) +{ + var webGLData; + + if (!webGL.data.length) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(webGL.gl); + webGLData.mode = type; + webGL.data.push(webGLData); + } + else + { + webGLData = webGL.data[webGL.data.length-1]; + + if (webGLData.mode !== type || type === 1) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(webGL.gl); + webGLData.mode = type; + webGL.data.push(webGLData); + } + } + + webGLData.dirty = true; + + return webGLData; +}; + +/** + * Builds a rectangle to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildRectangle = function (graphicsData, webGLData) +{ + // --- // + // need to convert points to a nice regular data + // + var rectData = graphicsData.shape; + var x = rectData.x; + var y = rectData.y; + var width = rectData.width; + var height = rectData.height; + + if (graphicsData.fill) + { + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var verts = webGLData.points; + var indices = webGLData.indices; + + var vertPos = verts.length/6; + + // start + verts.push(x, y); + verts.push(r, g, b, alpha); + + verts.push(x + width, y); + verts.push(r, g, b, alpha); + + verts.push(x , y + height); + verts.push(r, g, b, alpha); + + verts.push(x + width, y + height); + verts.push(r, g, b, alpha); + + // insert 2 dead triangles.. + indices.push(vertPos, vertPos, vertPos+1, vertPos+2, vertPos+3, vertPos+3); + } + + if (graphicsData.lineWidth) + { + var tempPoints = graphicsData.points; + + graphicsData.points = [x, y, + x + width, y, + x + width, y + height, + x, y + height, + x, y]; + + + this.buildLine(graphicsData, webGLData); + + graphicsData.points = tempPoints; + } +}; + +/** + * Builds a rounded rectangle to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildRoundedRectangle = function (graphicsData, webGLData) +{ + var rrectData = graphicsData.shape; + var x = rrectData.x; + var y = rrectData.y; + var width = rrectData.width; + var height = rrectData.height; + + var radius = rrectData.radius; + + var recPoints = []; + recPoints.push(x, y + radius); + recPoints = recPoints.concat(this.quadraticBezierCurve(x, y + height - radius, x, y + height, x + radius, y + height)); + recPoints = recPoints.concat(this.quadraticBezierCurve(x + width - radius, y + height, x + width, y + height, x + width, y + height - radius)); + recPoints = recPoints.concat(this.quadraticBezierCurve(x + width, y + radius, x + width, y, x + width - radius, y)); + recPoints = recPoints.concat(this.quadraticBezierCurve(x + radius, y, x, y, x, y + radius)); + + if (graphicsData.fill) + { + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var verts = webGLData.points; + var indices = webGLData.indices; + + var vecPos = verts.length/6; + + var triangles = utils.PolyK.Triangulate(recPoints); + + // + + var i = 0; + for (i = 0; i < triangles.length; i+=3) + { + indices.push(triangles[i] + vecPos); + indices.push(triangles[i] + vecPos); + indices.push(triangles[i+1] + vecPos); + indices.push(triangles[i+2] + vecPos); + indices.push(triangles[i+2] + vecPos); + } + + for (i = 0; i < recPoints.length; i++) + { + verts.push(recPoints[i], recPoints[++i], r, g, b, alpha); + } + } + + if (graphicsData.lineWidth) + { + var tempPoints = graphicsData.points; + + graphicsData.points = recPoints; + + this.buildLine(graphicsData, webGLData); + + graphicsData.points = tempPoints; + } +}; + +/** + * Calculate the points for a quadratic bezier curve. (helper function..) + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @private + * @param fromX {number} Origin point x + * @param fromY {number} Origin point x + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {number[]} + */ +GraphicsRenderer.prototype.quadraticBezierCurve = function (fromX, fromY, cpX, cpY, toX, toY) +{ + + var xa, + ya, + xb, + yb, + x, + y, + n = 20, + points = []; + + function getPt(n1 , n2, perc) { + var diff = n2 - n1; + + return n1 + ( diff * perc ); + } + + var j = 0; + for (var i = 0; i <= n; i++ ) { + j = i / n; + + // The Green Line + xa = getPt( fromX , cpX , j ); + ya = getPt( fromY , cpY , j ); + xb = getPt( cpX , toX , j ); + yb = getPt( cpY , toY , j ); + + // The Black Dot + x = getPt( xa , xb , j ); + y = getPt( ya , yb , j ); + + points.push(x, y); + } + return points; +}; + +/** + * Builds a circle to draw + * + * @private + * @param graphicsData {Graphics} The graphics object to draw + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildCircle = function (graphicsData, webGLData) +{ + // need to convert points to a nice regular data + var circleData = graphicsData.shape; + var x = circleData.x; + var y = circleData.y; + var width; + var height; + + // TODO - bit hacky?? + if (graphicsData.type === CONST.SHAPES.CIRC) + { + width = circleData.radius; + height = circleData.radius; + } + else + { + width = circleData.width; + height = circleData.height; + } + + var totalSegs = 40; + var seg = (Math.PI * 2) / totalSegs ; + + var i = 0; + + if (graphicsData.fill) + { + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var verts = webGLData.points; + var indices = webGLData.indices; + + var vecPos = verts.length/6; + + indices.push(vecPos); + + for (i = 0; i < totalSegs + 1 ; i++) + { + verts.push(x,y, r, g, b, alpha); + + verts.push(x + Math.sin(seg * i) * width, + y + Math.cos(seg * i) * height, + r, g, b, alpha); + + indices.push(vecPos++, vecPos++); + } + + indices.push(vecPos-1); + } + + if (graphicsData.lineWidth) + { + var tempPoints = graphicsData.points; + + graphicsData.points = []; + + for (i = 0; i < totalSegs + 1; i++) + { + graphicsData.points.push(x + Math.sin(seg * i) * width, + y + Math.cos(seg * i) * height); + } + + this.buildLine(graphicsData, webGLData); + + graphicsData.points = tempPoints; + } +}; + +/** + * Builds a line to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildLine = function (graphicsData, webGLData) +{ + // TODO OPTIMISE! + var i = 0; + var points = graphicsData.points; + + if (points.length === 0) + { + return; + } + + // if the line width is an odd number add 0.5 to align to a whole pixel + if (graphicsData.lineWidth%2) + { + for (i = 0; i < points.length; i++) + { + points[i] += 0.5; + } + } + + // get first and last point.. figure out the middle! + var firstPoint = new math.Point(points[0], points[1]); + var lastPoint = new math.Point(points[points.length - 2], points[points.length - 1]); + + // if the first point is the last point - gonna have issues :) + if (firstPoint.x === lastPoint.x && firstPoint.y === lastPoint.y) + { + // need to clone as we are going to slightly modify the shape.. + points = points.slice(); + + points.pop(); + points.pop(); + + lastPoint = new math.Point(points[points.length - 2], points[points.length - 1]); + + var midPointX = lastPoint.x + (firstPoint.x - lastPoint.x) *0.5; + var midPointY = lastPoint.y + (firstPoint.y - lastPoint.y) *0.5; + + points.unshift(midPointX, midPointY); + points.push(midPointX, midPointY); + } + + var verts = webGLData.points; + var indices = webGLData.indices; + var length = points.length / 2; + var indexCount = points.length; + var indexStart = verts.length/6; + + // DRAW the Line + var width = graphicsData.lineWidth / 2; + + // sort color + var color = utils.hex2rgb(graphicsData.lineColor); + var alpha = graphicsData.lineAlpha; + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var px, py, p1x, p1y, p2x, p2y, p3x, p3y; + var perpx, perpy, perp2x, perp2y, perp3x, perp3y; + var a1, b1, c1, a2, b2, c2; + var denom, pdist, dist; + + p1x = points[0]; + p1y = points[1]; + + p2x = points[2]; + p2y = points[3]; + + perpx = -(p1y - p2y); + perpy = p1x - p2x; + + dist = Math.sqrt(perpx*perpx + perpy*perpy); + + perpx /= dist; + perpy /= dist; + perpx *= width; + perpy *= width; + + // start + verts.push(p1x - perpx , p1y - perpy, + r, g, b, alpha); + + verts.push(p1x + perpx , p1y + perpy, + r, g, b, alpha); + + for (i = 1; i < length-1; i++) + { + p1x = points[(i-1)*2]; + p1y = points[(i-1)*2 + 1]; + + p2x = points[(i)*2]; + p2y = points[(i)*2 + 1]; + + p3x = points[(i+1)*2]; + p3y = points[(i+1)*2 + 1]; + + perpx = -(p1y - p2y); + perpy = p1x - p2x; + + dist = Math.sqrt(perpx*perpx + perpy*perpy); + perpx /= dist; + perpy /= dist; + perpx *= width; + perpy *= width; + + perp2x = -(p2y - p3y); + perp2y = p2x - p3x; + + dist = Math.sqrt(perp2x*perp2x + perp2y*perp2y); + perp2x /= dist; + perp2y /= dist; + perp2x *= width; + perp2y *= width; + + a1 = (-perpy + p1y) - (-perpy + p2y); + b1 = (-perpx + p2x) - (-perpx + p1x); + c1 = (-perpx + p1x) * (-perpy + p2y) - (-perpx + p2x) * (-perpy + p1y); + a2 = (-perp2y + p3y) - (-perp2y + p2y); + b2 = (-perp2x + p2x) - (-perp2x + p3x); + c2 = (-perp2x + p3x) * (-perp2y + p2y) - (-perp2x + p2x) * (-perp2y + p3y); + + denom = a1*b2 - a2*b1; + + if (Math.abs(denom) < 0.1 ) + { + + denom+=10.1; + verts.push(p2x - perpx , p2y - perpy, + r, g, b, alpha); + + verts.push(p2x + perpx , p2y + perpy, + r, g, b, alpha); + + continue; + } + + px = (b1*c2 - b2*c1)/denom; + py = (a2*c1 - a1*c2)/denom; + + + pdist = (px -p2x) * (px -p2x) + (py -p2y) + (py -p2y); + + + if (pdist > 140 * 140) + { + perp3x = perpx - perp2x; + perp3y = perpy - perp2y; + + dist = Math.sqrt(perp3x*perp3x + perp3y*perp3y); + perp3x /= dist; + perp3y /= dist; + perp3x *= width; + perp3y *= width; + + verts.push(p2x - perp3x, p2y -perp3y); + verts.push(r, g, b, alpha); + + verts.push(p2x + perp3x, p2y +perp3y); + verts.push(r, g, b, alpha); + + verts.push(p2x - perp3x, p2y -perp3y); + verts.push(r, g, b, alpha); + + indexCount++; + } + else + { + + verts.push(px , py); + verts.push(r, g, b, alpha); + + verts.push(p2x - (px-p2x), p2y - (py - p2y)); + verts.push(r, g, b, alpha); + } + } + + p1x = points[(length-2)*2]; + p1y = points[(length-2)*2 + 1]; + + p2x = points[(length-1)*2]; + p2y = points[(length-1)*2 + 1]; + + perpx = -(p1y - p2y); + perpy = p1x - p2x; + + dist = Math.sqrt(perpx*perpx + perpy*perpy); + perpx /= dist; + perpy /= dist; + perpx *= width; + perpy *= width; + + verts.push(p2x - perpx , p2y - perpy); + verts.push(r, g, b, alpha); + + verts.push(p2x + perpx , p2y + perpy); + verts.push(r, g, b, alpha); + + indices.push(indexStart); + + for (i = 0; i < indexCount; i++) + { + indices.push(indexStart++); + } + + indices.push(indexStart-1); +}; + +/** + * Builds a complex polygon to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildComplexPoly = function (graphicsData, webGLData) +{ + //TODO - no need to copy this as it gets turned into a FLoat32Array anyways.. + var points = graphicsData.points.slice(); + + if (points.length < 6) + { + return; + } + + // get first and last point.. figure out the middle! + var indices = webGLData.indices; + webGLData.points = points; + webGLData.alpha = graphicsData.fillAlpha; + webGLData.color = utils.hex2rgb(graphicsData.fillColor); + + // calclate the bounds.. + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + var x,y; + + // get size.. + for (var i = 0; i < points.length; i+=2) + { + x = points[i]; + y = points[i+1]; + + minX = x < minX ? x : minX; + maxX = x > maxX ? x : maxX; + + minY = y < minY ? y : minY; + maxY = y > maxY ? y : maxY; + } + + // add a quad to the end cos there is no point making another buffer! + points.push(minX, minY, + maxX, minY, + maxX, maxY, + minX, maxY); + + // push a quad onto the end.. + + //TODO - this aint needed! + var length = points.length / 2; + for (i = 0; i < length; i++) + { + indices.push( i ); + } + +}; + +/** + * Builds a polygon to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildPoly = function (graphicsData, webGLData) +{ + var points = graphicsData.points; + + if (points.length < 6) + { + return; + } + + // get first and last point.. figure out the middle! + var verts = webGLData.points; + var indices = webGLData.indices; + + var length = points.length / 2; + + // sort color + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var triangles = utils.PolyK.Triangulate(points); + + if (!triangles) { + return false; + } + + var vertPos = verts.length / 6; + + var i = 0; + + for (i = 0; i < triangles.length; i+=3) + { + indices.push(triangles[i] + vertPos); + indices.push(triangles[i] + vertPos); + indices.push(triangles[i+1] + vertPos); + indices.push(triangles[i+2] +vertPos); + indices.push(triangles[i+2] + vertPos); + } + + for (i = 0; i < length; i++) + { + verts.push(points[i * 2], points[i * 2 + 1], + r, g, b, alpha); + } + + return true; +}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 547bdc7..b0189c5 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,6 +1,4 @@ -var SpriteRenderer = require('./utils/SpriteRenderer'), - GraphicsRenderer = require('./utils/GraphicsRenderer'), - WebGLFastSpriteBatch = require('./utils/WebGLFastSpriteBatch'), +var WebGLFastSpriteBatch = require('./utils/WebGLFastSpriteBatch'), WebGLShaderManager = require('./managers/WebGLShaderManager'), WebGLMaskManager = require('./managers/WebGLMaskManager'), WebGLFilterManager = require('./managers/WebGLFilterManager'), @@ -28,21 +26,21 @@ * @param [options.preserveDrawingBuffer=false] {boolean} enables drawing buffer preservation, enable this if you need to call toDataUrl on the webgl context * @param [options.resolution=1] {number} the resolution of the renderer retina would be 2 */ -function WebGLRenderer(width, height, options) +function WebGLRenderer(width, height, options) { utils.sayHello('webGL'); - if (options) + if (options) { - for (var i in CONST.defaultRenderOptions) + for (var i in CONST.defaultRenderOptions) { - if (typeof options[i] === 'undefined') + if (typeof options[i] === 'undefined') { options[i] = CONST.defaultRenderOptions[i]; } } } - else + else { options = CONST.defaultRenderOptions; } @@ -191,14 +189,6 @@ /** * Manages the rendering of sprites - * @member {SpriteRenderer} - */ - this.spriteRenderer = new SpriteRenderer(this); - - this.graphicsRenderer = new GraphicsRenderer(this); - - /** - * Manages the rendering of sprites * @member {WebGLFastSpriteBatch} */ this.fastSpriteBatch = new WebGLFastSpriteBatch(this); @@ -245,8 +235,17 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - this.currentRenderer = this.spriteRenderer; + /** + * Manages the renderer of specific objects. + * + * @member {object} + */ + this.objectRenderers = {}; + // create an instance of each registered object renderer + for (var o in WebGLRenderer._objectRenderers) { + this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); + } } // constructor @@ -255,21 +254,20 @@ utils.eventTarget.mixin(WebGLRenderer.prototype); -Object.defineProperties(WebGLRenderer.prototype, -{ +Object.defineProperties(WebGLRenderer.prototype, { /** * The background color to fill if not transparent * * @member {number} * @memberof WebGLRenderer# */ - backgroundColor: + backgroundColor: { - get: function () + get: function () { return this._backgroundColor; }, - set: function (val) + set: function (val) { this._backgroundColor = val; utils.hex2rgb(val, this._backgroundColorRgb); @@ -281,12 +279,12 @@ * * @private */ -WebGLRenderer.prototype._initContext = function () +WebGLRenderer.prototype._initContext = function () { var gl = this.view.getContext('webgl', this._contextOptions) || this.view.getContext('experimental-webgl', this._contextOptions); this.gl = gl; - if (!gl) + if (!gl) { // fail, not able to get a context throw new Error('This browser does not support webGL. Try using the canvas renderer'); @@ -312,10 +310,10 @@ * * @param object {DisplayObject} the object to be rendered */ -WebGLRenderer.prototype.render = function (object) +WebGLRenderer.prototype.render = function (object) { // no point rendering if our context has been blown up! - if (this.gl.isContextLost()) + if (this.gl.isContextLost()) { return; } @@ -336,13 +334,13 @@ // make sure we are bound to the main frame buffer gl.bindFramebuffer(gl.FRAMEBUFFER, null); - if (this.clearBeforeRender) + if (this.clearBeforeRender) { - if (this.transparent) + if (this.transparent) { gl.clearColor(0, 0, 0, 0); } - else + else { gl.clearColor(this._backgroundColorRgb[0], this._backgroundColorRgb[1], this._backgroundColorRgb[2], 1); } @@ -360,7 +358,7 @@ * @param projection {Point} The projection * @param buffer {Array} a standard WebGL buffer */ -WebGLRenderer.prototype.renderDisplayObject = function (displayObject, projection, buffer) +WebGLRenderer.prototype.renderDisplayObject = function (displayObject, projection, buffer) { this.blendModeManager.setBlendMode(CONST.blendModes.NORMAL); @@ -386,9 +384,9 @@ this.currentRenderer.flush(); }; -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) +WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) { - if(this.currentRenderer === objectRenderer) + if (this.currentRenderer === objectRenderer) { return; } @@ -396,7 +394,7 @@ this.currentRenderer.stop(); this.currentRenderer = objectRenderer; this.currentRenderer.start(); -} +}; /** * Resizes the webGL view to the specified width and height. @@ -404,7 +402,7 @@ * @param width {number} the new width of the webGL view * @param height {number} the new height of the webGL view */ -WebGLRenderer.prototype.resize = function (width, height) +WebGLRenderer.prototype.resize = function (width, height) { this.width = width * this.resolution; this.height = height * this.resolution; @@ -412,7 +410,7 @@ this.view.width = this.width; this.view.height = this.height; - if (this.autoResize) + if (this.autoResize) { this.view.style.width = this.width / this.resolution + 'px'; this.view.style.height = this.height / this.resolution + 'px'; @@ -429,18 +427,18 @@ * * @param texture {BaseTexture|Texture} the texture to update */ -WebGLRenderer.prototype.updateTexture = function (texture) +WebGLRenderer.prototype.updateTexture = function (texture) { texture = texture.baseTexture || texture; - if (!texture.hasLoaded) + if (!texture.hasLoaded) { return; } var gl = this.gl; - if (!texture._glTextures[gl.id]) + if (!texture._glTextures[gl.id]) { texture._glTextures[gl.id] = gl.createTexture(); texture.on('update', this._boundUpdateTexture); @@ -455,22 +453,22 @@ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, texture.scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); - if (texture.mipmap && utils.isPowerOfTwo(texture.width, texture.height)) + if (texture.mipmap && utils.isPowerOfTwo(texture.width, texture.height)) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); gl.generateMipmap(gl.TEXTURE_2D); } - else + else { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); } - if (!texture._powerOf2) + if (!texture._powerOf2) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); } - else + else { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); @@ -479,16 +477,16 @@ return texture._glTextures[gl.id]; }; -WebGLRenderer.prototype.destroyTexture = function (texture) +WebGLRenderer.prototype.destroyTexture = function (texture) { texture = texture.baseTexture || texture; - if (!texture.hasLoaded) + if (!texture.hasLoaded) { return; } - if (texture._glTextures[this.gl.id]) + if (texture._glTextures[this.gl.id]) { this.gl.deleteTexture(texture._glTextures[this.gl.id]); } @@ -500,7 +498,7 @@ * @param event {Event} * @private */ -WebGLRenderer.prototype.handleContextLost = function (event) +WebGLRenderer.prototype.handleContextLost = function (event) { event.preventDefault(); }; @@ -511,12 +509,12 @@ * @param event {Event} * @private */ -WebGLRenderer.prototype.handleContextRestored = function () +WebGLRenderer.prototype.handleContextRestored = function () { this._initContext(); // empty all the ol gl textures as they are useless now - for (var key in utils.TextureCache) + for (var key in utils.TextureCache) { var texture = utils.TextureCache[key].baseTexture; texture._glTextures = []; @@ -528,9 +526,9 @@ * * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ -WebGLRenderer.prototype.destroy = function (removeView) +WebGLRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parent) + if (removeView && this.view.parent) { this.view.parent.removeChild(this.view); } @@ -541,10 +539,15 @@ // time to create the render managers! each one focuses on managine a state in webGL this.shaderManager.destroy(); - this.spriteRenderer.destroy(); this.maskManager.destroy(); this.filterManager.destroy(); + for (var o in this.objectRenderers) { + this.objectRenderers[o].destroy(); + this.objectRenderers[o] = null; + } + + this.objectRenderers = null; // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -574,7 +577,6 @@ this.drawCount = 0; this.shaderManager = null; - this.spriteRenderer = null; this.maskManager = null; this.filterManager = null; this.stencilManager = null; @@ -591,11 +593,11 @@ * * @private */ -WebGLRenderer.prototype._mapBlendModes = function () +WebGLRenderer.prototype._mapBlendModes = function () { var gl = this.gl; - if (!this.blendModes) + if (!this.blendModes) { this.blendModes = {}; @@ -620,3 +622,9 @@ }; WebGLRenderer.glContextId = 0; +WebGLRenderer._objectRenderers = {}; + +WebGLRenderer.registerObjectRenderer = function (name, ctor) { + WebGLRenderer._objectRenderers[name] = ctor; +}; + diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js index 773cc5d..e81ca56 100644 --- a/src/core/renderers/webgl/managers/WebGLMaskManager.js +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -20,11 +20,11 @@ */ WebGLMaskManager.prototype.pushMask = function (maskData) { - this.renderer.setObjectRenderer( this.renderer.graphicsRenderer ); + this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); if (maskData.dirty) { - this.renderer.graphicsRenderer.updateGraphics(maskData, this.renderer.gl); + this.renderer.objectRenderers.graphics.updateGraphics(maskData, this.renderer.gl); } if (!maskData._webGL[this.renderer.gl.id].data.length) @@ -42,7 +42,7 @@ */ WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.setObjectRenderer( this.renderer.graphicsRenderer ); - + this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.stencilManager.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); }; diff --git a/src/core/renderers/webgl/utils/GraphicsRenderer.js b/src/core/renderers/webgl/utils/GraphicsRenderer.js deleted file mode 100644 index 6713275..0000000 --- a/src/core/renderers/webgl/utils/GraphicsRenderer.js +++ /dev/null @@ -1,894 +0,0 @@ -var utils = require('../../../utils'), - math = require('../../../math'), - CONST = require('../../../const'), - WebGLGraphicsData = require('./WebGLGraphicsData'); - -/** - * A set of functions used by the webGL renderer to draw the primitive graphics data - * - * @namespace PIXI - * @private - */ -var WebGLGraphics = module.exports = {}; - -/** - * Renders the graphics object - * - * @static - * @private - * @param graphics {Graphics} - * @param renderer {WebGLRenderer} - */ - -var GraphicsRenderer = function(renderer) -{ - this.renderer = renderer; - this.graphicsDataPool = []; -} - -module.exports = GraphicsRenderer; - -GraphicsRenderer.prototype.start = function() -{ - // set the shader.. - -} - -GraphicsRenderer.prototype.stop = function() -{ - // flush! -} - -GraphicsRenderer.prototype.flush = function() -{ - // flush! -} - - - -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var projection = renderer.projection, - offset = renderer.offset, - shader = renderer.shaderManager.primitiveShader, - webGLData; - - if (graphics.dirty) - { - this.updateGraphics(graphics, gl); - } - - var webGL = graphics._webGL[gl.id]; - - // This could be speeded up for sure! - - renderer.blendModeManager.setBlendMode( graphics.blendMode ); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.stencilManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.stencilManager.popStencil(graphics, webGLData, renderer); - } - else - { - webGLData = webGL.data[i]; - - - renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); - shader = renderer.shaderManager.primitiveShader; - gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); - - gl.uniform1f(shader.uniforms.flipY._location, 1); - - gl.uniform2f(shader.uniforms.projectionVector._location, projection.x, -projection.y); - gl.uniform2f(shader.uniforms.offsetVector._location, -offset.x, -offset.y); - - gl.uniform3fv(shader.uniforms.tint._location, utils.hex2rgb(graphics.tint)); - - gl.uniform1f(shader.uniforms.alpha._location, graphics.worldAlpha); - - - gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); - - gl.vertexAttribPointer(shader.attributes.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); - gl.vertexAttribPointer(shader.attributes.aColor, 4, gl.FLOAT, false,4 * 6, 2 * 4); - - // set the index buffer! - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); - gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); - } - } -} - -/** - * Updates the graphics object - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object to update - * @param gl {WebGLContext} the current WebGL drawing context - */ - -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[gl.id]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[gl.id] = {lastIndex:0, data:[], gl:gl}; - } - - // flag the graphics as not dirty as we are about to update it... - graphics.dirty = false; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty) - { - graphics.clearDirty = false; - - // lop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - graphicsData.reset(); - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - if (data.type === CONST.SHAPES.POLY) - { - // need to add the points the the graphics object.. - data.points = data.shape.points.slice(); - if (data.shape.closed) - { - // close the poly if the value is true! - if (data.points[0] !== data.points[data.points.length-2] || data.points[1] !== data.points[data.points.length-1]) - { - data.points.push(data.points[0], data.points[1]); - } - } - - // MAKE SURE WE HAVE THE CORRECT TYPE.. - if (data.fill) - { - if (data.points.length >= 6) - { - if (data.points.length < 6 * 2) - { - webGLData = this.switchMode(webGL, 0); - - var canDrawUsingSimple = this.buildPoly(data, webGLData); - // console.log(canDrawUsingSimple); - - if (!canDrawUsingSimple) - { - // console.log("<>>>") - webGLData = this.switchMode(webGL, 1); - this.buildComplexPoly(data, webGLData); - } - - } - else - { - webGLData = this.switchMode(webGL, 1); - this.buildComplexPoly(data, webGLData); - } - } - } - - if (data.lineWidth > 0) - { - webGLData = this.switchMode(webGL, 0); - this.buildLine(data, webGLData); - } - } - else - { - webGLData = this.switchMode(webGL, 0); - - if (data.type === CONST.SHAPES.RECT) - { - this.buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - this.buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - this.buildRoundedRectangle(data, webGLData); - } - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -} - -/** - * @static - * @private - * @param webGL {WebGLContext} - * @param type {number} - */ -GraphicsRenderer.prototype.switchMode = function (webGL, type) -{ - var webGLData; - - if (!webGL.data.length) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(webGL.gl); - webGLData.mode = type; - webGL.data.push(webGLData); - } - else - { - webGLData = webGL.data[webGL.data.length-1]; - - if (webGLData.mode !== type || type === 1) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(webGL.gl); - webGLData.mode = type; - webGL.data.push(webGLData); - } - } - - webGLData.dirty = true; - - return webGLData; -}; - - - - - - -/** - * Builds a rectangle to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildRectangle = function (graphicsData, webGLData) -{ - // --- // - // need to convert points to a nice regular data - // - var rectData = graphicsData.shape; - var x = rectData.x; - var y = rectData.y; - var width = rectData.width; - var height = rectData.height; - - if (graphicsData.fill) - { - var color = utils.hex2rgb(graphicsData.fillColor); - var alpha = graphicsData.fillAlpha; - - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var verts = webGLData.points; - var indices = webGLData.indices; - - var vertPos = verts.length/6; - - // start - verts.push(x, y); - verts.push(r, g, b, alpha); - - verts.push(x + width, y); - verts.push(r, g, b, alpha); - - verts.push(x , y + height); - verts.push(r, g, b, alpha); - - verts.push(x + width, y + height); - verts.push(r, g, b, alpha); - - // insert 2 dead triangles.. - indices.push(vertPos, vertPos, vertPos+1, vertPos+2, vertPos+3, vertPos+3); - } - - if (graphicsData.lineWidth) - { - var tempPoints = graphicsData.points; - - graphicsData.points = [x, y, - x + width, y, - x + width, y + height, - x, y + height, - x, y]; - - - this.buildLine(graphicsData, webGLData); - - graphicsData.points = tempPoints; - } -}; - -/** - * Builds a rounded rectangle to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildRoundedRectangle = function (graphicsData, webGLData) -{ - var rrectData = graphicsData.shape; - var x = rrectData.x; - var y = rrectData.y; - var width = rrectData.width; - var height = rrectData.height; - - var radius = rrectData.radius; - - var recPoints = []; - recPoints.push(x, y + radius); - recPoints = recPoints.concat(this.quadraticBezierCurve(x, y + height - radius, x, y + height, x + radius, y + height)); - recPoints = recPoints.concat(this.quadraticBezierCurve(x + width - radius, y + height, x + width, y + height, x + width, y + height - radius)); - recPoints = recPoints.concat(this.quadraticBezierCurve(x + width, y + radius, x + width, y, x + width - radius, y)); - recPoints = recPoints.concat(this.quadraticBezierCurve(x + radius, y, x, y, x, y + radius)); - - if (graphicsData.fill) - { - var color = utils.hex2rgb(graphicsData.fillColor); - var alpha = graphicsData.fillAlpha; - - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var verts = webGLData.points; - var indices = webGLData.indices; - - var vecPos = verts.length/6; - - var triangles = utils.PolyK.Triangulate(recPoints); - - // - - var i = 0; - for (i = 0; i < triangles.length; i+=3) - { - indices.push(triangles[i] + vecPos); - indices.push(triangles[i] + vecPos); - indices.push(triangles[i+1] + vecPos); - indices.push(triangles[i+2] + vecPos); - indices.push(triangles[i+2] + vecPos); - } - - for (i = 0; i < recPoints.length; i++) - { - verts.push(recPoints[i], recPoints[++i], r, g, b, alpha); - } - } - - if (graphicsData.lineWidth) - { - var tempPoints = graphicsData.points; - - graphicsData.points = recPoints; - - this.buildLine(graphicsData, webGLData); - - graphicsData.points = tempPoints; - } -}; - -/** - * Calculate the points for a quadratic bezier curve. (helper function..) - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @static - * @private - * @param fromX {number} Origin point x - * @param fromY {number} Origin point x - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {number[]} - */ -GraphicsRenderer.prototype.quadraticBezierCurve = function (fromX, fromY, cpX, cpY, toX, toY) -{ - - var xa, - ya, - xb, - yb, - x, - y, - n = 20, - points = []; - - function getPt(n1 , n2, perc) { - var diff = n2 - n1; - - return n1 + ( diff * perc ); - } - - var j = 0; - for (var i = 0; i <= n; i++ ) { - j = i / n; - - // The Green Line - xa = getPt( fromX , cpX , j ); - ya = getPt( fromY , cpY , j ); - xb = getPt( cpX , toX , j ); - yb = getPt( cpY , toY , j ); - - // The Black Dot - x = getPt( xa , xb , j ); - y = getPt( ya , yb , j ); - - points.push(x, y); - } - return points; -}; - -/** - * Builds a circle to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object to draw - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildCircle = function (graphicsData, webGLData) -{ - // need to convert points to a nice regular data - var circleData = graphicsData.shape; - var x = circleData.x; - var y = circleData.y; - var width; - var height; - - // TODO - bit hacky?? - if (graphicsData.type === CONST.SHAPES.CIRC) - { - width = circleData.radius; - height = circleData.radius; - } - else - { - width = circleData.width; - height = circleData.height; - } - - var totalSegs = 40; - var seg = (Math.PI * 2) / totalSegs ; - - var i = 0; - - if (graphicsData.fill) - { - var color = utils.hex2rgb(graphicsData.fillColor); - var alpha = graphicsData.fillAlpha; - - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var verts = webGLData.points; - var indices = webGLData.indices; - - var vecPos = verts.length/6; - - indices.push(vecPos); - - for (i = 0; i < totalSegs + 1 ; i++) - { - verts.push(x,y, r, g, b, alpha); - - verts.push(x + Math.sin(seg * i) * width, - y + Math.cos(seg * i) * height, - r, g, b, alpha); - - indices.push(vecPos++, vecPos++); - } - - indices.push(vecPos-1); - } - - if (graphicsData.lineWidth) - { - var tempPoints = graphicsData.points; - - graphicsData.points = []; - - for (i = 0; i < totalSegs + 1; i++) - { - graphicsData.points.push(x + Math.sin(seg * i) * width, - y + Math.cos(seg * i) * height); - } - - this.buildLine(graphicsData, webGLData); - - graphicsData.points = tempPoints; - } -}; - -/** - * Builds a line to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildLine = function (graphicsData, webGLData) -{ - // TODO OPTIMISE! - var i = 0; - var points = graphicsData.points; - - if (points.length === 0) - { - return; - } - - // if the line width is an odd number add 0.5 to align to a whole pixel - if (graphicsData.lineWidth%2) - { - for (i = 0; i < points.length; i++) - { - points[i] += 0.5; - } - } - - // get first and last point.. figure out the middle! - var firstPoint = new math.Point(points[0], points[1]); - var lastPoint = new math.Point(points[points.length - 2], points[points.length - 1]); - - // if the first point is the last point - gonna have issues :) - if (firstPoint.x === lastPoint.x && firstPoint.y === lastPoint.y) - { - // need to clone as we are going to slightly modify the shape.. - points = points.slice(); - - points.pop(); - points.pop(); - - lastPoint = new math.Point(points[points.length - 2], points[points.length - 1]); - - var midPointX = lastPoint.x + (firstPoint.x - lastPoint.x) *0.5; - var midPointY = lastPoint.y + (firstPoint.y - lastPoint.y) *0.5; - - points.unshift(midPointX, midPointY); - points.push(midPointX, midPointY); - } - - var verts = webGLData.points; - var indices = webGLData.indices; - var length = points.length / 2; - var indexCount = points.length; - var indexStart = verts.length/6; - - // DRAW the Line - var width = graphicsData.lineWidth / 2; - - // sort color - var color = utils.hex2rgb(graphicsData.lineColor); - var alpha = graphicsData.lineAlpha; - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var px, py, p1x, p1y, p2x, p2y, p3x, p3y; - var perpx, perpy, perp2x, perp2y, perp3x, perp3y; - var a1, b1, c1, a2, b2, c2; - var denom, pdist, dist; - - p1x = points[0]; - p1y = points[1]; - - p2x = points[2]; - p2y = points[3]; - - perpx = -(p1y - p2y); - perpy = p1x - p2x; - - dist = Math.sqrt(perpx*perpx + perpy*perpy); - - perpx /= dist; - perpy /= dist; - perpx *= width; - perpy *= width; - - // start - verts.push(p1x - perpx , p1y - perpy, - r, g, b, alpha); - - verts.push(p1x + perpx , p1y + perpy, - r, g, b, alpha); - - for (i = 1; i < length-1; i++) - { - p1x = points[(i-1)*2]; - p1y = points[(i-1)*2 + 1]; - - p2x = points[(i)*2]; - p2y = points[(i)*2 + 1]; - - p3x = points[(i+1)*2]; - p3y = points[(i+1)*2 + 1]; - - perpx = -(p1y - p2y); - perpy = p1x - p2x; - - dist = Math.sqrt(perpx*perpx + perpy*perpy); - perpx /= dist; - perpy /= dist; - perpx *= width; - perpy *= width; - - perp2x = -(p2y - p3y); - perp2y = p2x - p3x; - - dist = Math.sqrt(perp2x*perp2x + perp2y*perp2y); - perp2x /= dist; - perp2y /= dist; - perp2x *= width; - perp2y *= width; - - a1 = (-perpy + p1y) - (-perpy + p2y); - b1 = (-perpx + p2x) - (-perpx + p1x); - c1 = (-perpx + p1x) * (-perpy + p2y) - (-perpx + p2x) * (-perpy + p1y); - a2 = (-perp2y + p3y) - (-perp2y + p2y); - b2 = (-perp2x + p2x) - (-perp2x + p3x); - c2 = (-perp2x + p3x) * (-perp2y + p2y) - (-perp2x + p2x) * (-perp2y + p3y); - - denom = a1*b2 - a2*b1; - - if (Math.abs(denom) < 0.1 ) - { - - denom+=10.1; - verts.push(p2x - perpx , p2y - perpy, - r, g, b, alpha); - - verts.push(p2x + perpx , p2y + perpy, - r, g, b, alpha); - - continue; - } - - px = (b1*c2 - b2*c1)/denom; - py = (a2*c1 - a1*c2)/denom; - - - pdist = (px -p2x) * (px -p2x) + (py -p2y) + (py -p2y); - - - if (pdist > 140 * 140) - { - perp3x = perpx - perp2x; - perp3y = perpy - perp2y; - - dist = Math.sqrt(perp3x*perp3x + perp3y*perp3y); - perp3x /= dist; - perp3y /= dist; - perp3x *= width; - perp3y *= width; - - verts.push(p2x - perp3x, p2y -perp3y); - verts.push(r, g, b, alpha); - - verts.push(p2x + perp3x, p2y +perp3y); - verts.push(r, g, b, alpha); - - verts.push(p2x - perp3x, p2y -perp3y); - verts.push(r, g, b, alpha); - - indexCount++; - } - else - { - - verts.push(px , py); - verts.push(r, g, b, alpha); - - verts.push(p2x - (px-p2x), p2y - (py - p2y)); - verts.push(r, g, b, alpha); - } - } - - p1x = points[(length-2)*2]; - p1y = points[(length-2)*2 + 1]; - - p2x = points[(length-1)*2]; - p2y = points[(length-1)*2 + 1]; - - perpx = -(p1y - p2y); - perpy = p1x - p2x; - - dist = Math.sqrt(perpx*perpx + perpy*perpy); - perpx /= dist; - perpy /= dist; - perpx *= width; - perpy *= width; - - verts.push(p2x - perpx , p2y - perpy); - verts.push(r, g, b, alpha); - - verts.push(p2x + perpx , p2y + perpy); - verts.push(r, g, b, alpha); - - indices.push(indexStart); - - for (i = 0; i < indexCount; i++) - { - indices.push(indexStart++); - } - - indices.push(indexStart-1); -}; - -/** - * Builds a complex polygon to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildComplexPoly = function (graphicsData, webGLData) -{ - //TODO - no need to copy this as it gets turned into a FLoat32Array anyways.. - var points = graphicsData.points.slice(); - - if (points.length < 6) - { - return; - } - - // get first and last point.. figure out the middle! - var indices = webGLData.indices; - webGLData.points = points; - webGLData.alpha = graphicsData.fillAlpha; - webGLData.color = utils.hex2rgb(graphicsData.fillColor); - - // calclate the bounds.. - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - var x,y; - - // get size.. - for (var i = 0; i < points.length; i+=2) - { - x = points[i]; - y = points[i+1]; - - minX = x < minX ? x : minX; - maxX = x > maxX ? x : maxX; - - minY = y < minY ? y : minY; - maxY = y > maxY ? y : maxY; - } - - // add a quad to the end cos there is no point making another buffer! - points.push(minX, minY, - maxX, minY, - maxX, maxY, - minX, maxY); - - // push a quad onto the end.. - - //TODO - this aint needed! - var length = points.length / 2; - for (i = 0; i < length; i++) - { - indices.push( i ); - } - -}; - -/** - * Builds a polygon to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildPoly = function (graphicsData, webGLData) -{ - var points = graphicsData.points; - - if (points.length < 6) - { - return; - } - - // get first and last point.. figure out the middle! - var verts = webGLData.points; - var indices = webGLData.indices; - - var length = points.length / 2; - - // sort color - var color = utils.hex2rgb(graphicsData.fillColor); - var alpha = graphicsData.fillAlpha; - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var triangles = utils.PolyK.Triangulate(points); - - if (!triangles) { - return false; - } - - var vertPos = verts.length / 6; - - var i = 0; - - for (i = 0; i < triangles.length; i+=3) - { - indices.push(triangles[i] + vertPos); - indices.push(triangles[i] + vertPos); - indices.push(triangles[i+1] + vertPos); - indices.push(triangles[i+2] +vertPos); - indices.push(triangles[i+2] + vertPos); - } - - for (i = 0; i < length; i++) - { - verts.push(points[i * 2], points[i * 2 + 1], - r, g, b, alpha); - } - - return true; -}; - -GraphicsRenderer.graphicsDataPool = []; diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 0669e79..c2294f9 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,7 +500,7 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.spriteRenderer.flush(); + renderer.objectRenderers.sprite.flush(); renderer.filterManager.pushFilter(this._filterBlock); } diff --git a/src/core/display/Sprite.js b/src/core/display/Sprite.js deleted file mode 100644 index e4e9d01..0000000 --- a/src/core/display/Sprite.js +++ /dev/null @@ -1,461 +0,0 @@ -var math = require('../math'), - Texture = require('../textures/Texture'), - DisplayObjectContainer = require('./DisplayObjectContainer'), - CanvasTinter = require('../renderers/canvas/utils/CanvasTinter'), - utils = require('../utils'), - CONST = require('../const'); - -/** - * The Sprite object is the base for all textured objects that are rendered to the screen - * - * A sprite can be created directly from an image like this: - * - * ```js - * var sprite = new Sprite.fromImage('assets/image.png'); - * ``` - * - * @class Sprite - * @extends DisplayObjectContainer - * @namespace PIXI - * @param texture {Texture} The texture for this sprite - */ -function Sprite(texture) -{ - DisplayObjectContainer.call(this); - - /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting than anchor to 0.5,0.5 means the textures origin is centered - * Setting the anchor to 1,1 would mean the textures origin points will be the bottom right corner - * - * @member {Point} - */ - this.anchor = new math.Point(); - - /** - * The texture that the sprite is using - * - * @member {Texture} - * @private - */ - this._texture = null; - - /** - * The width of the sprite (this is initially set by the texture) - * - * @member {number} - * @private - */ - this._width = 0; - - /** - * The height of the sprite (this is initially set by the texture) - * - * @member {number} - * @private - */ - this._height = 0; - - /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the sprite. Set to CONST.blendModes.NORMAL to remove any blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. - * - * @member {AbstractFilter} - */ - this.shader = null; - - this.renderable = true; - - // call texture setter - this.texture = texture || Texture.EMPTY; -} - -Sprite.prototype.destroy = function (destroyTexture, destroyBaseTexture) -{ - DisplayObjectContainer.prototype.destroy.call(this); - - this.anchor = null; - - if (destroyTexture) - { - this._texture.destroy(destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// constructor -Sprite.prototype = Object.create(DisplayObjectContainer.prototype); -Sprite.prototype.constructor = Sprite; -module.exports = Sprite; - -Object.defineProperties(Sprite.prototype, { - /** - * The width of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - width: { - get: function () - { - return this.scale.x * this.texture.frame.width; - }, - set: function (value) - { - this.scale.x = value / this.texture.frame.width; - this._width = value; - } - }, - - /** - * The height of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - height: { - get: function () - { - return this.scale.y * this.texture.frame.height; - }, - set: function (value) - { - this.scale.y = value / this.texture.frame.height; - this._height = value; - } - }, - - /** - * The height of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - texture: { - get: function () - { - return this._texture; - }, - set: function (value) - { - if (this._texture === value) - { - return; - } - - this._texture = value; - this.cachedTint = 0xFFFFFF; - - if (value) - { - // wait for the texture to load - if (value.baseTexture.hasLoaded) - { - this._onTextureUpdate(); - } - else - { - value.once('update', this._onTextureUpdate.bind(this)); - } - } - } - }, -}); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = this._width / this.texture.frame.width; - } - - if (this._height) - { - this.scale.y = this._height / this.texture.frame.height; - } -}; - -Sprite.prototype._renderWebGL = function (renderer) -{ - - // this is where content gets renderd.. - // watch this space for a little render state manager.. - renderer.setObjectRenderer(renderer.spriteRenderer); - renderer.spriteRenderer.render(this); -}; - -/** - * Returns the bounds of the Sprite as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param matrix {Matrix} the transformation matrix of the sprite - * @return {Rectangle} the framing rectangle - */ -Sprite.prototype.getBounds = function (matrix) -{ - var width = this.texture.frame.width; - var height = this.texture.frame.height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = matrix || this.worldTransform ; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var minX, - maxX, - minY, - maxY; - - if(b === 0 && c === 0) - { - // scale may be negative! - if (a < 0) - { - a *= -1; - } - - if (d < 0) - { - d *= -1; - } - - // this means there is no rotation going on right? RIGHT? - // if thats the case then we can avoid checking the bound values! yay - minX = a * w1 + tx; - maxX = a * w0 + tx; - minY = d * h1 + ty; - maxY = d * h0 + ty; - } - else - { - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {CanvasRenderer} The renderer -*/ -Sprite.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || this.texture.crop.width <= 0 || this.texture.crop.height <= 0) - { - return; - } - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - renderer.context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - // Ignore null sources - if (this.texture.valid) - { - var resolution = this.texture.baseTexture.resolution / renderer.resolution; - - renderer.context.globalAlpha = this.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for this texture - if (renderer.smoothProperty && renderer.scaleMode !== this.texture.baseTexture.scaleMode) - { - renderer.scaleMode = this.texture.baseTexture.scaleMode; - renderer.context[renderer.smoothProperty] = (renderer.scaleMode === CONST.scaleModes.LINEAR); - } - - // If the texture is trimmed we offset by the trim x/y, otherwise we use the frame dimensions - var dx = (this.texture.trim ? this.texture.trim.x : 0) - (this.anchor.x * this.texture.trim.width); - var dy = (this.texture.trim ? this.texture.trim.y : 0) - (this.anchor.y * this.texture.trim.height); - - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - this.worldTransform.a, - this.worldTransform.b, - this.worldTransform.c, - this.worldTransform.d, - (this.worldTransform.tx * renderer.resolution) | 0, - (this.worldTransform.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - this.worldTransform.a, - this.worldTransform.b, - this.worldTransform.c, - this.worldTransform.d, - this.worldTransform.tx * renderer.resolution, - this.worldTransform.ty * renderer.resolution - ); - } - - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - // TODO clean up caching - how to clean up the caches? - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - - renderer.context.drawImage( - this.tintedTexture, - 0, - 0, - this.texture.crop.width, - this.texture.crop.height, - dx / resolution, - dy / resolution, - this.texture.crop.width / resolution, - this.texture.crop.height / resolution - ); - } - else - { - renderer.context.drawImage( - this.texture.baseTexture.source, - this.texture.crop.x, - this.texture.crop.y, - this.texture.crop.width, - this.texture.crop.height, - dx / resolution, - dy / resolution, - this.texture.crop.width / resolution, - this.texture.crop.height / resolution - ); - } - } - - for (var i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -// some helper functions.. - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {String} The frame Id of the texture in the cache - * @return {Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache' + this); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {String} The image url of the texture - * @return {Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/display/SpriteBatch.js b/src/core/display/SpriteBatch.js deleted file mode 100644 index c196fef..0000000 --- a/src/core/display/SpriteBatch.js +++ /dev/null @@ -1,183 +0,0 @@ -var DisplayObjectContainer = require('./DisplayObjectContainer'), - WebGLFastSpriteBatch = require('../renderers/webgl/utils/WebGLFastSpriteBatch'); - -/** - * The SpriteBatch class is a really fast version of the DisplayObjectContainer built solely for speed, - * so use when you need a lot of sprites or particles. The tradeoff of the SpriteBatch is that advanced - * functionality will not work. SpriteBatch implements only the basic object transform (position, scale, rotation). - * Any other functionality like tinting, masking, etc will not work on sprites in this batch. - * - * It's extremely easy to use : - * - * ```js - * var container = new SpriteBatch(); - * - * for(var i = 0; i < 100; ++i) - * { - * var sprite = new PIXI.Sprite.fromImage("myImage.png"); - * container.addChild(sprite); - * } - * ``` - * - * And here you have a hundred sprites that will be renderer at the speed of light. - * - * @class - * @namespace PIXI - */ - -//TODO RENAME to PARTICLE CONTAINER? -function SpriteBatch() -{ - DisplayObjectContainer.call(this); -} - -SpriteBatch.prototype = Object.create(DisplayObjectContainer.prototype); -SpriteBatch.prototype.constructor = SpriteBatch; -module.exports = SpriteBatch; - -/** - * Updates the object transform for rendering - * - * @private - */ -SpriteBatch.prototype.updateTransform = function () -{ - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.DisplayObjectContainer.prototype.updateTransform.call( this ); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} The webgl renderer - * @private - */ -SpriteBatch.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || !this.children.length) - { - return; - } - - renderer.spriteBatch.stop(); - - renderer.shaderManager.setShader(renderer.shaderManager.fastShader); - - renderer.fastSpriteBatch.begin(this); - renderer.fastSpriteBatch.render(this); - - renderer.spriteBatch.start(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} The canvas renderer - * @private - */ -SpriteBatch.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || !this.children.length) - { - return; - } - - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - var frame = child.texture.frame; - - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) - { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) - { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx, - transform.ty - ); - - isRotated = false; - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5) | 0, - ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5) | 0, - frame.width * child.scale.x, - frame.height * child.scale.y - ); - } - else - { - if (!isRotated) - { - isRotated = true; - } - - child.displayObjectUpdateTransform(); - - var childTransform = child.worldTransform; - - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx | 0, - childTransform.ty | 0 - ); - } - else - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx, - childTransform.ty - ); - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - ((child.anchor.x) * (-frame.width) + 0.5) | 0, - ((child.anchor.y) * (-frame.height) + 0.5) | 0, - frame.width, - frame.height - ); - } - } -}; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js index 70d28b1..baab0f8 100644 --- a/src/core/primitives/Graphics.js +++ b/src/core/primitives/Graphics.js @@ -738,17 +738,17 @@ return; } - + */ - + if (this.glDirty) { this.dirty = true; this.glDirty = false; } - renderer.setObjectRenderer(renderer.graphicsRenderer); - renderer.graphicsRenderer.render(this); + renderer.setObjectRenderer(renderer.objectRenderers.graphics); + renderer.objectRenderers.graphics.render(this); }; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js new file mode 100644 index 0000000..55dc7a5 --- /dev/null +++ b/src/core/primitives/GraphicsRenderer.js @@ -0,0 +1,874 @@ +var utils = require('../../../utils'), + math = require('../../../math'), + CONST = require('../../../const'), + ObjectRenderer = require('../renderers/webgl/utils/ObjectRenderer'), + WebGLRenderer = require('../renderers/webgl/WebGLRenderer'), + WebGLGraphicsData = require('./WebGLGraphicsData'); + +/** + * Renders the graphics object. + * + * @class + * @private + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this object renderer works for. + */ +function GraphicsRenderer(renderer) +{ + ObjectRenderer.call(this, renderer); + + this.graphicsDataPool = []; +} + +GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); +GraphicsRenderer.prototype.constructor = GraphicsRenderer; +module.exports = GraphicsRenderer; + +WebGLRenderer.registerObjectRenderer('graphics', GraphicsRenderer); + +/** + * Destroys this renderer. + * + */ +GraphicsRenderer.prototype.destroy = function () { + ObjectRenderer.prototype.destroy.call(this); + + this.graphicsDataPool = null; +}; + +/** + * Renders a graphics object. + * + * @param graphics {Graphics} The graphics object to render. + */ +GraphicsRenderer.prototype.render = function(graphics) +{ + var renderer = this.renderer; + var gl = renderer.gl; + + var projection = renderer.projection, + offset = renderer.offset, + shader = renderer.shaderManager.primitiveShader, + webGLData; + + if (graphics.dirty) + { + this.updateGraphics(graphics, gl); + } + + var webGL = graphics._webGL[gl.id]; + + // This could be speeded up for sure! + + renderer.blendModeManager.setBlendMode( graphics.blendMode ); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.stencilManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.stencilManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.primitiveShader; + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniform1f(shader.uniforms.flipY._location, 1); + + gl.uniform2f(shader.uniforms.projectionVector._location, projection.x, -projection.y); + gl.uniform2f(shader.uniforms.offsetVector._location, -offset.x, -offset.y); + + gl.uniform3fv(shader.uniforms.tint._location, utils.hex2rgb(graphics.tint)); + + gl.uniform1f(shader.uniforms.alpha._location, graphics.worldAlpha); + + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.attributes.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); + gl.vertexAttribPointer(shader.attributes.aColor, 4, gl.FLOAT, false,4 * 6, 2 * 4); + + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + } + } +} + +/** + * Updates the graphics object + * + * @private + * @param graphicsData {Graphics} The graphics object to update + * @param gl {WebGLContext} the current WebGL drawing context + */ +GraphicsRenderer.prototype.updateGraphics = function(graphics) +{ + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[gl.id]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[gl.id] = {lastIndex:0, data:[], gl:gl}; + } + + // flag the graphics as not dirty as we are about to update it... + graphics.dirty = false; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty) + { + graphics.clearDirty = false; + + // lop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + graphicsData.reset(); + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + if (data.type === CONST.SHAPES.POLY) + { + // need to add the points the the graphics object.. + data.points = data.shape.points.slice(); + if (data.shape.closed) + { + // close the poly if the value is true! + if (data.points[0] !== data.points[data.points.length-2] || data.points[1] !== data.points[data.points.length-1]) + { + data.points.push(data.points[0], data.points[1]); + } + } + + // MAKE SURE WE HAVE THE CORRECT TYPE.. + if (data.fill) + { + if (data.points.length >= 6) + { + if (data.points.length < 6 * 2) + { + webGLData = this.switchMode(webGL, 0); + + var canDrawUsingSimple = this.buildPoly(data, webGLData); + // console.log(canDrawUsingSimple); + + if (!canDrawUsingSimple) + { + // console.log("<>>>") + webGLData = this.switchMode(webGL, 1); + this.buildComplexPoly(data, webGLData); + } + + } + else + { + webGLData = this.switchMode(webGL, 1); + this.buildComplexPoly(data, webGLData); + } + } + } + + if (data.lineWidth > 0) + { + webGLData = this.switchMode(webGL, 0); + this.buildLine(data, webGLData); + } + } + else + { + webGLData = this.switchMode(webGL, 0); + + if (data.type === CONST.SHAPES.RECT) + { + this.buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + this.buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + this.buildRoundedRectangle(data, webGLData); + } + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } +} + +/** + * + * + * @private + * @param webGL {WebGLContext} + * @param type {number} + */ +GraphicsRenderer.prototype.switchMode = function (webGL, type) +{ + var webGLData; + + if (!webGL.data.length) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(webGL.gl); + webGLData.mode = type; + webGL.data.push(webGLData); + } + else + { + webGLData = webGL.data[webGL.data.length-1]; + + if (webGLData.mode !== type || type === 1) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(webGL.gl); + webGLData.mode = type; + webGL.data.push(webGLData); + } + } + + webGLData.dirty = true; + + return webGLData; +}; + +/** + * Builds a rectangle to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildRectangle = function (graphicsData, webGLData) +{ + // --- // + // need to convert points to a nice regular data + // + var rectData = graphicsData.shape; + var x = rectData.x; + var y = rectData.y; + var width = rectData.width; + var height = rectData.height; + + if (graphicsData.fill) + { + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var verts = webGLData.points; + var indices = webGLData.indices; + + var vertPos = verts.length/6; + + // start + verts.push(x, y); + verts.push(r, g, b, alpha); + + verts.push(x + width, y); + verts.push(r, g, b, alpha); + + verts.push(x , y + height); + verts.push(r, g, b, alpha); + + verts.push(x + width, y + height); + verts.push(r, g, b, alpha); + + // insert 2 dead triangles.. + indices.push(vertPos, vertPos, vertPos+1, vertPos+2, vertPos+3, vertPos+3); + } + + if (graphicsData.lineWidth) + { + var tempPoints = graphicsData.points; + + graphicsData.points = [x, y, + x + width, y, + x + width, y + height, + x, y + height, + x, y]; + + + this.buildLine(graphicsData, webGLData); + + graphicsData.points = tempPoints; + } +}; + +/** + * Builds a rounded rectangle to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildRoundedRectangle = function (graphicsData, webGLData) +{ + var rrectData = graphicsData.shape; + var x = rrectData.x; + var y = rrectData.y; + var width = rrectData.width; + var height = rrectData.height; + + var radius = rrectData.radius; + + var recPoints = []; + recPoints.push(x, y + radius); + recPoints = recPoints.concat(this.quadraticBezierCurve(x, y + height - radius, x, y + height, x + radius, y + height)); + recPoints = recPoints.concat(this.quadraticBezierCurve(x + width - radius, y + height, x + width, y + height, x + width, y + height - radius)); + recPoints = recPoints.concat(this.quadraticBezierCurve(x + width, y + radius, x + width, y, x + width - radius, y)); + recPoints = recPoints.concat(this.quadraticBezierCurve(x + radius, y, x, y, x, y + radius)); + + if (graphicsData.fill) + { + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var verts = webGLData.points; + var indices = webGLData.indices; + + var vecPos = verts.length/6; + + var triangles = utils.PolyK.Triangulate(recPoints); + + // + + var i = 0; + for (i = 0; i < triangles.length; i+=3) + { + indices.push(triangles[i] + vecPos); + indices.push(triangles[i] + vecPos); + indices.push(triangles[i+1] + vecPos); + indices.push(triangles[i+2] + vecPos); + indices.push(triangles[i+2] + vecPos); + } + + for (i = 0; i < recPoints.length; i++) + { + verts.push(recPoints[i], recPoints[++i], r, g, b, alpha); + } + } + + if (graphicsData.lineWidth) + { + var tempPoints = graphicsData.points; + + graphicsData.points = recPoints; + + this.buildLine(graphicsData, webGLData); + + graphicsData.points = tempPoints; + } +}; + +/** + * Calculate the points for a quadratic bezier curve. (helper function..) + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @private + * @param fromX {number} Origin point x + * @param fromY {number} Origin point x + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {number[]} + */ +GraphicsRenderer.prototype.quadraticBezierCurve = function (fromX, fromY, cpX, cpY, toX, toY) +{ + + var xa, + ya, + xb, + yb, + x, + y, + n = 20, + points = []; + + function getPt(n1 , n2, perc) { + var diff = n2 - n1; + + return n1 + ( diff * perc ); + } + + var j = 0; + for (var i = 0; i <= n; i++ ) { + j = i / n; + + // The Green Line + xa = getPt( fromX , cpX , j ); + ya = getPt( fromY , cpY , j ); + xb = getPt( cpX , toX , j ); + yb = getPt( cpY , toY , j ); + + // The Black Dot + x = getPt( xa , xb , j ); + y = getPt( ya , yb , j ); + + points.push(x, y); + } + return points; +}; + +/** + * Builds a circle to draw + * + * @private + * @param graphicsData {Graphics} The graphics object to draw + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildCircle = function (graphicsData, webGLData) +{ + // need to convert points to a nice regular data + var circleData = graphicsData.shape; + var x = circleData.x; + var y = circleData.y; + var width; + var height; + + // TODO - bit hacky?? + if (graphicsData.type === CONST.SHAPES.CIRC) + { + width = circleData.radius; + height = circleData.radius; + } + else + { + width = circleData.width; + height = circleData.height; + } + + var totalSegs = 40; + var seg = (Math.PI * 2) / totalSegs ; + + var i = 0; + + if (graphicsData.fill) + { + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var verts = webGLData.points; + var indices = webGLData.indices; + + var vecPos = verts.length/6; + + indices.push(vecPos); + + for (i = 0; i < totalSegs + 1 ; i++) + { + verts.push(x,y, r, g, b, alpha); + + verts.push(x + Math.sin(seg * i) * width, + y + Math.cos(seg * i) * height, + r, g, b, alpha); + + indices.push(vecPos++, vecPos++); + } + + indices.push(vecPos-1); + } + + if (graphicsData.lineWidth) + { + var tempPoints = graphicsData.points; + + graphicsData.points = []; + + for (i = 0; i < totalSegs + 1; i++) + { + graphicsData.points.push(x + Math.sin(seg * i) * width, + y + Math.cos(seg * i) * height); + } + + this.buildLine(graphicsData, webGLData); + + graphicsData.points = tempPoints; + } +}; + +/** + * Builds a line to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildLine = function (graphicsData, webGLData) +{ + // TODO OPTIMISE! + var i = 0; + var points = graphicsData.points; + + if (points.length === 0) + { + return; + } + + // if the line width is an odd number add 0.5 to align to a whole pixel + if (graphicsData.lineWidth%2) + { + for (i = 0; i < points.length; i++) + { + points[i] += 0.5; + } + } + + // get first and last point.. figure out the middle! + var firstPoint = new math.Point(points[0], points[1]); + var lastPoint = new math.Point(points[points.length - 2], points[points.length - 1]); + + // if the first point is the last point - gonna have issues :) + if (firstPoint.x === lastPoint.x && firstPoint.y === lastPoint.y) + { + // need to clone as we are going to slightly modify the shape.. + points = points.slice(); + + points.pop(); + points.pop(); + + lastPoint = new math.Point(points[points.length - 2], points[points.length - 1]); + + var midPointX = lastPoint.x + (firstPoint.x - lastPoint.x) *0.5; + var midPointY = lastPoint.y + (firstPoint.y - lastPoint.y) *0.5; + + points.unshift(midPointX, midPointY); + points.push(midPointX, midPointY); + } + + var verts = webGLData.points; + var indices = webGLData.indices; + var length = points.length / 2; + var indexCount = points.length; + var indexStart = verts.length/6; + + // DRAW the Line + var width = graphicsData.lineWidth / 2; + + // sort color + var color = utils.hex2rgb(graphicsData.lineColor); + var alpha = graphicsData.lineAlpha; + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var px, py, p1x, p1y, p2x, p2y, p3x, p3y; + var perpx, perpy, perp2x, perp2y, perp3x, perp3y; + var a1, b1, c1, a2, b2, c2; + var denom, pdist, dist; + + p1x = points[0]; + p1y = points[1]; + + p2x = points[2]; + p2y = points[3]; + + perpx = -(p1y - p2y); + perpy = p1x - p2x; + + dist = Math.sqrt(perpx*perpx + perpy*perpy); + + perpx /= dist; + perpy /= dist; + perpx *= width; + perpy *= width; + + // start + verts.push(p1x - perpx , p1y - perpy, + r, g, b, alpha); + + verts.push(p1x + perpx , p1y + perpy, + r, g, b, alpha); + + for (i = 1; i < length-1; i++) + { + p1x = points[(i-1)*2]; + p1y = points[(i-1)*2 + 1]; + + p2x = points[(i)*2]; + p2y = points[(i)*2 + 1]; + + p3x = points[(i+1)*2]; + p3y = points[(i+1)*2 + 1]; + + perpx = -(p1y - p2y); + perpy = p1x - p2x; + + dist = Math.sqrt(perpx*perpx + perpy*perpy); + perpx /= dist; + perpy /= dist; + perpx *= width; + perpy *= width; + + perp2x = -(p2y - p3y); + perp2y = p2x - p3x; + + dist = Math.sqrt(perp2x*perp2x + perp2y*perp2y); + perp2x /= dist; + perp2y /= dist; + perp2x *= width; + perp2y *= width; + + a1 = (-perpy + p1y) - (-perpy + p2y); + b1 = (-perpx + p2x) - (-perpx + p1x); + c1 = (-perpx + p1x) * (-perpy + p2y) - (-perpx + p2x) * (-perpy + p1y); + a2 = (-perp2y + p3y) - (-perp2y + p2y); + b2 = (-perp2x + p2x) - (-perp2x + p3x); + c2 = (-perp2x + p3x) * (-perp2y + p2y) - (-perp2x + p2x) * (-perp2y + p3y); + + denom = a1*b2 - a2*b1; + + if (Math.abs(denom) < 0.1 ) + { + + denom+=10.1; + verts.push(p2x - perpx , p2y - perpy, + r, g, b, alpha); + + verts.push(p2x + perpx , p2y + perpy, + r, g, b, alpha); + + continue; + } + + px = (b1*c2 - b2*c1)/denom; + py = (a2*c1 - a1*c2)/denom; + + + pdist = (px -p2x) * (px -p2x) + (py -p2y) + (py -p2y); + + + if (pdist > 140 * 140) + { + perp3x = perpx - perp2x; + perp3y = perpy - perp2y; + + dist = Math.sqrt(perp3x*perp3x + perp3y*perp3y); + perp3x /= dist; + perp3y /= dist; + perp3x *= width; + perp3y *= width; + + verts.push(p2x - perp3x, p2y -perp3y); + verts.push(r, g, b, alpha); + + verts.push(p2x + perp3x, p2y +perp3y); + verts.push(r, g, b, alpha); + + verts.push(p2x - perp3x, p2y -perp3y); + verts.push(r, g, b, alpha); + + indexCount++; + } + else + { + + verts.push(px , py); + verts.push(r, g, b, alpha); + + verts.push(p2x - (px-p2x), p2y - (py - p2y)); + verts.push(r, g, b, alpha); + } + } + + p1x = points[(length-2)*2]; + p1y = points[(length-2)*2 + 1]; + + p2x = points[(length-1)*2]; + p2y = points[(length-1)*2 + 1]; + + perpx = -(p1y - p2y); + perpy = p1x - p2x; + + dist = Math.sqrt(perpx*perpx + perpy*perpy); + perpx /= dist; + perpy /= dist; + perpx *= width; + perpy *= width; + + verts.push(p2x - perpx , p2y - perpy); + verts.push(r, g, b, alpha); + + verts.push(p2x + perpx , p2y + perpy); + verts.push(r, g, b, alpha); + + indices.push(indexStart); + + for (i = 0; i < indexCount; i++) + { + indices.push(indexStart++); + } + + indices.push(indexStart-1); +}; + +/** + * Builds a complex polygon to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildComplexPoly = function (graphicsData, webGLData) +{ + //TODO - no need to copy this as it gets turned into a FLoat32Array anyways.. + var points = graphicsData.points.slice(); + + if (points.length < 6) + { + return; + } + + // get first and last point.. figure out the middle! + var indices = webGLData.indices; + webGLData.points = points; + webGLData.alpha = graphicsData.fillAlpha; + webGLData.color = utils.hex2rgb(graphicsData.fillColor); + + // calclate the bounds.. + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + var x,y; + + // get size.. + for (var i = 0; i < points.length; i+=2) + { + x = points[i]; + y = points[i+1]; + + minX = x < minX ? x : minX; + maxX = x > maxX ? x : maxX; + + minY = y < minY ? y : minY; + maxY = y > maxY ? y : maxY; + } + + // add a quad to the end cos there is no point making another buffer! + points.push(minX, minY, + maxX, minY, + maxX, maxY, + minX, maxY); + + // push a quad onto the end.. + + //TODO - this aint needed! + var length = points.length / 2; + for (i = 0; i < length; i++) + { + indices.push( i ); + } + +}; + +/** + * Builds a polygon to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildPoly = function (graphicsData, webGLData) +{ + var points = graphicsData.points; + + if (points.length < 6) + { + return; + } + + // get first and last point.. figure out the middle! + var verts = webGLData.points; + var indices = webGLData.indices; + + var length = points.length / 2; + + // sort color + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var triangles = utils.PolyK.Triangulate(points); + + if (!triangles) { + return false; + } + + var vertPos = verts.length / 6; + + var i = 0; + + for (i = 0; i < triangles.length; i+=3) + { + indices.push(triangles[i] + vertPos); + indices.push(triangles[i] + vertPos); + indices.push(triangles[i+1] + vertPos); + indices.push(triangles[i+2] +vertPos); + indices.push(triangles[i+2] + vertPos); + } + + for (i = 0; i < length; i++) + { + verts.push(points[i * 2], points[i * 2 + 1], + r, g, b, alpha); + } + + return true; +}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 547bdc7..b0189c5 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,6 +1,4 @@ -var SpriteRenderer = require('./utils/SpriteRenderer'), - GraphicsRenderer = require('./utils/GraphicsRenderer'), - WebGLFastSpriteBatch = require('./utils/WebGLFastSpriteBatch'), +var WebGLFastSpriteBatch = require('./utils/WebGLFastSpriteBatch'), WebGLShaderManager = require('./managers/WebGLShaderManager'), WebGLMaskManager = require('./managers/WebGLMaskManager'), WebGLFilterManager = require('./managers/WebGLFilterManager'), @@ -28,21 +26,21 @@ * @param [options.preserveDrawingBuffer=false] {boolean} enables drawing buffer preservation, enable this if you need to call toDataUrl on the webgl context * @param [options.resolution=1] {number} the resolution of the renderer retina would be 2 */ -function WebGLRenderer(width, height, options) +function WebGLRenderer(width, height, options) { utils.sayHello('webGL'); - if (options) + if (options) { - for (var i in CONST.defaultRenderOptions) + for (var i in CONST.defaultRenderOptions) { - if (typeof options[i] === 'undefined') + if (typeof options[i] === 'undefined') { options[i] = CONST.defaultRenderOptions[i]; } } } - else + else { options = CONST.defaultRenderOptions; } @@ -191,14 +189,6 @@ /** * Manages the rendering of sprites - * @member {SpriteRenderer} - */ - this.spriteRenderer = new SpriteRenderer(this); - - this.graphicsRenderer = new GraphicsRenderer(this); - - /** - * Manages the rendering of sprites * @member {WebGLFastSpriteBatch} */ this.fastSpriteBatch = new WebGLFastSpriteBatch(this); @@ -245,8 +235,17 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - this.currentRenderer = this.spriteRenderer; + /** + * Manages the renderer of specific objects. + * + * @member {object} + */ + this.objectRenderers = {}; + // create an instance of each registered object renderer + for (var o in WebGLRenderer._objectRenderers) { + this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); + } } // constructor @@ -255,21 +254,20 @@ utils.eventTarget.mixin(WebGLRenderer.prototype); -Object.defineProperties(WebGLRenderer.prototype, -{ +Object.defineProperties(WebGLRenderer.prototype, { /** * The background color to fill if not transparent * * @member {number} * @memberof WebGLRenderer# */ - backgroundColor: + backgroundColor: { - get: function () + get: function () { return this._backgroundColor; }, - set: function (val) + set: function (val) { this._backgroundColor = val; utils.hex2rgb(val, this._backgroundColorRgb); @@ -281,12 +279,12 @@ * * @private */ -WebGLRenderer.prototype._initContext = function () +WebGLRenderer.prototype._initContext = function () { var gl = this.view.getContext('webgl', this._contextOptions) || this.view.getContext('experimental-webgl', this._contextOptions); this.gl = gl; - if (!gl) + if (!gl) { // fail, not able to get a context throw new Error('This browser does not support webGL. Try using the canvas renderer'); @@ -312,10 +310,10 @@ * * @param object {DisplayObject} the object to be rendered */ -WebGLRenderer.prototype.render = function (object) +WebGLRenderer.prototype.render = function (object) { // no point rendering if our context has been blown up! - if (this.gl.isContextLost()) + if (this.gl.isContextLost()) { return; } @@ -336,13 +334,13 @@ // make sure we are bound to the main frame buffer gl.bindFramebuffer(gl.FRAMEBUFFER, null); - if (this.clearBeforeRender) + if (this.clearBeforeRender) { - if (this.transparent) + if (this.transparent) { gl.clearColor(0, 0, 0, 0); } - else + else { gl.clearColor(this._backgroundColorRgb[0], this._backgroundColorRgb[1], this._backgroundColorRgb[2], 1); } @@ -360,7 +358,7 @@ * @param projection {Point} The projection * @param buffer {Array} a standard WebGL buffer */ -WebGLRenderer.prototype.renderDisplayObject = function (displayObject, projection, buffer) +WebGLRenderer.prototype.renderDisplayObject = function (displayObject, projection, buffer) { this.blendModeManager.setBlendMode(CONST.blendModes.NORMAL); @@ -386,9 +384,9 @@ this.currentRenderer.flush(); }; -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) +WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) { - if(this.currentRenderer === objectRenderer) + if (this.currentRenderer === objectRenderer) { return; } @@ -396,7 +394,7 @@ this.currentRenderer.stop(); this.currentRenderer = objectRenderer; this.currentRenderer.start(); -} +}; /** * Resizes the webGL view to the specified width and height. @@ -404,7 +402,7 @@ * @param width {number} the new width of the webGL view * @param height {number} the new height of the webGL view */ -WebGLRenderer.prototype.resize = function (width, height) +WebGLRenderer.prototype.resize = function (width, height) { this.width = width * this.resolution; this.height = height * this.resolution; @@ -412,7 +410,7 @@ this.view.width = this.width; this.view.height = this.height; - if (this.autoResize) + if (this.autoResize) { this.view.style.width = this.width / this.resolution + 'px'; this.view.style.height = this.height / this.resolution + 'px'; @@ -429,18 +427,18 @@ * * @param texture {BaseTexture|Texture} the texture to update */ -WebGLRenderer.prototype.updateTexture = function (texture) +WebGLRenderer.prototype.updateTexture = function (texture) { texture = texture.baseTexture || texture; - if (!texture.hasLoaded) + if (!texture.hasLoaded) { return; } var gl = this.gl; - if (!texture._glTextures[gl.id]) + if (!texture._glTextures[gl.id]) { texture._glTextures[gl.id] = gl.createTexture(); texture.on('update', this._boundUpdateTexture); @@ -455,22 +453,22 @@ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, texture.scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); - if (texture.mipmap && utils.isPowerOfTwo(texture.width, texture.height)) + if (texture.mipmap && utils.isPowerOfTwo(texture.width, texture.height)) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); gl.generateMipmap(gl.TEXTURE_2D); } - else + else { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); } - if (!texture._powerOf2) + if (!texture._powerOf2) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); } - else + else { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); @@ -479,16 +477,16 @@ return texture._glTextures[gl.id]; }; -WebGLRenderer.prototype.destroyTexture = function (texture) +WebGLRenderer.prototype.destroyTexture = function (texture) { texture = texture.baseTexture || texture; - if (!texture.hasLoaded) + if (!texture.hasLoaded) { return; } - if (texture._glTextures[this.gl.id]) + if (texture._glTextures[this.gl.id]) { this.gl.deleteTexture(texture._glTextures[this.gl.id]); } @@ -500,7 +498,7 @@ * @param event {Event} * @private */ -WebGLRenderer.prototype.handleContextLost = function (event) +WebGLRenderer.prototype.handleContextLost = function (event) { event.preventDefault(); }; @@ -511,12 +509,12 @@ * @param event {Event} * @private */ -WebGLRenderer.prototype.handleContextRestored = function () +WebGLRenderer.prototype.handleContextRestored = function () { this._initContext(); // empty all the ol gl textures as they are useless now - for (var key in utils.TextureCache) + for (var key in utils.TextureCache) { var texture = utils.TextureCache[key].baseTexture; texture._glTextures = []; @@ -528,9 +526,9 @@ * * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ -WebGLRenderer.prototype.destroy = function (removeView) +WebGLRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parent) + if (removeView && this.view.parent) { this.view.parent.removeChild(this.view); } @@ -541,10 +539,15 @@ // time to create the render managers! each one focuses on managine a state in webGL this.shaderManager.destroy(); - this.spriteRenderer.destroy(); this.maskManager.destroy(); this.filterManager.destroy(); + for (var o in this.objectRenderers) { + this.objectRenderers[o].destroy(); + this.objectRenderers[o] = null; + } + + this.objectRenderers = null; // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -574,7 +577,6 @@ this.drawCount = 0; this.shaderManager = null; - this.spriteRenderer = null; this.maskManager = null; this.filterManager = null; this.stencilManager = null; @@ -591,11 +593,11 @@ * * @private */ -WebGLRenderer.prototype._mapBlendModes = function () +WebGLRenderer.prototype._mapBlendModes = function () { var gl = this.gl; - if (!this.blendModes) + if (!this.blendModes) { this.blendModes = {}; @@ -620,3 +622,9 @@ }; WebGLRenderer.glContextId = 0; +WebGLRenderer._objectRenderers = {}; + +WebGLRenderer.registerObjectRenderer = function (name, ctor) { + WebGLRenderer._objectRenderers[name] = ctor; +}; + diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js index 773cc5d..e81ca56 100644 --- a/src/core/renderers/webgl/managers/WebGLMaskManager.js +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -20,11 +20,11 @@ */ WebGLMaskManager.prototype.pushMask = function (maskData) { - this.renderer.setObjectRenderer( this.renderer.graphicsRenderer ); + this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); if (maskData.dirty) { - this.renderer.graphicsRenderer.updateGraphics(maskData, this.renderer.gl); + this.renderer.objectRenderers.graphics.updateGraphics(maskData, this.renderer.gl); } if (!maskData._webGL[this.renderer.gl.id].data.length) @@ -42,7 +42,7 @@ */ WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.setObjectRenderer( this.renderer.graphicsRenderer ); - + this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.stencilManager.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); }; diff --git a/src/core/renderers/webgl/utils/GraphicsRenderer.js b/src/core/renderers/webgl/utils/GraphicsRenderer.js deleted file mode 100644 index 6713275..0000000 --- a/src/core/renderers/webgl/utils/GraphicsRenderer.js +++ /dev/null @@ -1,894 +0,0 @@ -var utils = require('../../../utils'), - math = require('../../../math'), - CONST = require('../../../const'), - WebGLGraphicsData = require('./WebGLGraphicsData'); - -/** - * A set of functions used by the webGL renderer to draw the primitive graphics data - * - * @namespace PIXI - * @private - */ -var WebGLGraphics = module.exports = {}; - -/** - * Renders the graphics object - * - * @static - * @private - * @param graphics {Graphics} - * @param renderer {WebGLRenderer} - */ - -var GraphicsRenderer = function(renderer) -{ - this.renderer = renderer; - this.graphicsDataPool = []; -} - -module.exports = GraphicsRenderer; - -GraphicsRenderer.prototype.start = function() -{ - // set the shader.. - -} - -GraphicsRenderer.prototype.stop = function() -{ - // flush! -} - -GraphicsRenderer.prototype.flush = function() -{ - // flush! -} - - - -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var projection = renderer.projection, - offset = renderer.offset, - shader = renderer.shaderManager.primitiveShader, - webGLData; - - if (graphics.dirty) - { - this.updateGraphics(graphics, gl); - } - - var webGL = graphics._webGL[gl.id]; - - // This could be speeded up for sure! - - renderer.blendModeManager.setBlendMode( graphics.blendMode ); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.stencilManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.stencilManager.popStencil(graphics, webGLData, renderer); - } - else - { - webGLData = webGL.data[i]; - - - renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); - shader = renderer.shaderManager.primitiveShader; - gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); - - gl.uniform1f(shader.uniforms.flipY._location, 1); - - gl.uniform2f(shader.uniforms.projectionVector._location, projection.x, -projection.y); - gl.uniform2f(shader.uniforms.offsetVector._location, -offset.x, -offset.y); - - gl.uniform3fv(shader.uniforms.tint._location, utils.hex2rgb(graphics.tint)); - - gl.uniform1f(shader.uniforms.alpha._location, graphics.worldAlpha); - - - gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); - - gl.vertexAttribPointer(shader.attributes.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); - gl.vertexAttribPointer(shader.attributes.aColor, 4, gl.FLOAT, false,4 * 6, 2 * 4); - - // set the index buffer! - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); - gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); - } - } -} - -/** - * Updates the graphics object - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object to update - * @param gl {WebGLContext} the current WebGL drawing context - */ - -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[gl.id]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[gl.id] = {lastIndex:0, data:[], gl:gl}; - } - - // flag the graphics as not dirty as we are about to update it... - graphics.dirty = false; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty) - { - graphics.clearDirty = false; - - // lop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - graphicsData.reset(); - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - if (data.type === CONST.SHAPES.POLY) - { - // need to add the points the the graphics object.. - data.points = data.shape.points.slice(); - if (data.shape.closed) - { - // close the poly if the value is true! - if (data.points[0] !== data.points[data.points.length-2] || data.points[1] !== data.points[data.points.length-1]) - { - data.points.push(data.points[0], data.points[1]); - } - } - - // MAKE SURE WE HAVE THE CORRECT TYPE.. - if (data.fill) - { - if (data.points.length >= 6) - { - if (data.points.length < 6 * 2) - { - webGLData = this.switchMode(webGL, 0); - - var canDrawUsingSimple = this.buildPoly(data, webGLData); - // console.log(canDrawUsingSimple); - - if (!canDrawUsingSimple) - { - // console.log("<>>>") - webGLData = this.switchMode(webGL, 1); - this.buildComplexPoly(data, webGLData); - } - - } - else - { - webGLData = this.switchMode(webGL, 1); - this.buildComplexPoly(data, webGLData); - } - } - } - - if (data.lineWidth > 0) - { - webGLData = this.switchMode(webGL, 0); - this.buildLine(data, webGLData); - } - } - else - { - webGLData = this.switchMode(webGL, 0); - - if (data.type === CONST.SHAPES.RECT) - { - this.buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - this.buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - this.buildRoundedRectangle(data, webGLData); - } - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -} - -/** - * @static - * @private - * @param webGL {WebGLContext} - * @param type {number} - */ -GraphicsRenderer.prototype.switchMode = function (webGL, type) -{ - var webGLData; - - if (!webGL.data.length) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(webGL.gl); - webGLData.mode = type; - webGL.data.push(webGLData); - } - else - { - webGLData = webGL.data[webGL.data.length-1]; - - if (webGLData.mode !== type || type === 1) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(webGL.gl); - webGLData.mode = type; - webGL.data.push(webGLData); - } - } - - webGLData.dirty = true; - - return webGLData; -}; - - - - - - -/** - * Builds a rectangle to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildRectangle = function (graphicsData, webGLData) -{ - // --- // - // need to convert points to a nice regular data - // - var rectData = graphicsData.shape; - var x = rectData.x; - var y = rectData.y; - var width = rectData.width; - var height = rectData.height; - - if (graphicsData.fill) - { - var color = utils.hex2rgb(graphicsData.fillColor); - var alpha = graphicsData.fillAlpha; - - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var verts = webGLData.points; - var indices = webGLData.indices; - - var vertPos = verts.length/6; - - // start - verts.push(x, y); - verts.push(r, g, b, alpha); - - verts.push(x + width, y); - verts.push(r, g, b, alpha); - - verts.push(x , y + height); - verts.push(r, g, b, alpha); - - verts.push(x + width, y + height); - verts.push(r, g, b, alpha); - - // insert 2 dead triangles.. - indices.push(vertPos, vertPos, vertPos+1, vertPos+2, vertPos+3, vertPos+3); - } - - if (graphicsData.lineWidth) - { - var tempPoints = graphicsData.points; - - graphicsData.points = [x, y, - x + width, y, - x + width, y + height, - x, y + height, - x, y]; - - - this.buildLine(graphicsData, webGLData); - - graphicsData.points = tempPoints; - } -}; - -/** - * Builds a rounded rectangle to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildRoundedRectangle = function (graphicsData, webGLData) -{ - var rrectData = graphicsData.shape; - var x = rrectData.x; - var y = rrectData.y; - var width = rrectData.width; - var height = rrectData.height; - - var radius = rrectData.radius; - - var recPoints = []; - recPoints.push(x, y + radius); - recPoints = recPoints.concat(this.quadraticBezierCurve(x, y + height - radius, x, y + height, x + radius, y + height)); - recPoints = recPoints.concat(this.quadraticBezierCurve(x + width - radius, y + height, x + width, y + height, x + width, y + height - radius)); - recPoints = recPoints.concat(this.quadraticBezierCurve(x + width, y + radius, x + width, y, x + width - radius, y)); - recPoints = recPoints.concat(this.quadraticBezierCurve(x + radius, y, x, y, x, y + radius)); - - if (graphicsData.fill) - { - var color = utils.hex2rgb(graphicsData.fillColor); - var alpha = graphicsData.fillAlpha; - - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var verts = webGLData.points; - var indices = webGLData.indices; - - var vecPos = verts.length/6; - - var triangles = utils.PolyK.Triangulate(recPoints); - - // - - var i = 0; - for (i = 0; i < triangles.length; i+=3) - { - indices.push(triangles[i] + vecPos); - indices.push(triangles[i] + vecPos); - indices.push(triangles[i+1] + vecPos); - indices.push(triangles[i+2] + vecPos); - indices.push(triangles[i+2] + vecPos); - } - - for (i = 0; i < recPoints.length; i++) - { - verts.push(recPoints[i], recPoints[++i], r, g, b, alpha); - } - } - - if (graphicsData.lineWidth) - { - var tempPoints = graphicsData.points; - - graphicsData.points = recPoints; - - this.buildLine(graphicsData, webGLData); - - graphicsData.points = tempPoints; - } -}; - -/** - * Calculate the points for a quadratic bezier curve. (helper function..) - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @static - * @private - * @param fromX {number} Origin point x - * @param fromY {number} Origin point x - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {number[]} - */ -GraphicsRenderer.prototype.quadraticBezierCurve = function (fromX, fromY, cpX, cpY, toX, toY) -{ - - var xa, - ya, - xb, - yb, - x, - y, - n = 20, - points = []; - - function getPt(n1 , n2, perc) { - var diff = n2 - n1; - - return n1 + ( diff * perc ); - } - - var j = 0; - for (var i = 0; i <= n; i++ ) { - j = i / n; - - // The Green Line - xa = getPt( fromX , cpX , j ); - ya = getPt( fromY , cpY , j ); - xb = getPt( cpX , toX , j ); - yb = getPt( cpY , toY , j ); - - // The Black Dot - x = getPt( xa , xb , j ); - y = getPt( ya , yb , j ); - - points.push(x, y); - } - return points; -}; - -/** - * Builds a circle to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object to draw - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildCircle = function (graphicsData, webGLData) -{ - // need to convert points to a nice regular data - var circleData = graphicsData.shape; - var x = circleData.x; - var y = circleData.y; - var width; - var height; - - // TODO - bit hacky?? - if (graphicsData.type === CONST.SHAPES.CIRC) - { - width = circleData.radius; - height = circleData.radius; - } - else - { - width = circleData.width; - height = circleData.height; - } - - var totalSegs = 40; - var seg = (Math.PI * 2) / totalSegs ; - - var i = 0; - - if (graphicsData.fill) - { - var color = utils.hex2rgb(graphicsData.fillColor); - var alpha = graphicsData.fillAlpha; - - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var verts = webGLData.points; - var indices = webGLData.indices; - - var vecPos = verts.length/6; - - indices.push(vecPos); - - for (i = 0; i < totalSegs + 1 ; i++) - { - verts.push(x,y, r, g, b, alpha); - - verts.push(x + Math.sin(seg * i) * width, - y + Math.cos(seg * i) * height, - r, g, b, alpha); - - indices.push(vecPos++, vecPos++); - } - - indices.push(vecPos-1); - } - - if (graphicsData.lineWidth) - { - var tempPoints = graphicsData.points; - - graphicsData.points = []; - - for (i = 0; i < totalSegs + 1; i++) - { - graphicsData.points.push(x + Math.sin(seg * i) * width, - y + Math.cos(seg * i) * height); - } - - this.buildLine(graphicsData, webGLData); - - graphicsData.points = tempPoints; - } -}; - -/** - * Builds a line to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildLine = function (graphicsData, webGLData) -{ - // TODO OPTIMISE! - var i = 0; - var points = graphicsData.points; - - if (points.length === 0) - { - return; - } - - // if the line width is an odd number add 0.5 to align to a whole pixel - if (graphicsData.lineWidth%2) - { - for (i = 0; i < points.length; i++) - { - points[i] += 0.5; - } - } - - // get first and last point.. figure out the middle! - var firstPoint = new math.Point(points[0], points[1]); - var lastPoint = new math.Point(points[points.length - 2], points[points.length - 1]); - - // if the first point is the last point - gonna have issues :) - if (firstPoint.x === lastPoint.x && firstPoint.y === lastPoint.y) - { - // need to clone as we are going to slightly modify the shape.. - points = points.slice(); - - points.pop(); - points.pop(); - - lastPoint = new math.Point(points[points.length - 2], points[points.length - 1]); - - var midPointX = lastPoint.x + (firstPoint.x - lastPoint.x) *0.5; - var midPointY = lastPoint.y + (firstPoint.y - lastPoint.y) *0.5; - - points.unshift(midPointX, midPointY); - points.push(midPointX, midPointY); - } - - var verts = webGLData.points; - var indices = webGLData.indices; - var length = points.length / 2; - var indexCount = points.length; - var indexStart = verts.length/6; - - // DRAW the Line - var width = graphicsData.lineWidth / 2; - - // sort color - var color = utils.hex2rgb(graphicsData.lineColor); - var alpha = graphicsData.lineAlpha; - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var px, py, p1x, p1y, p2x, p2y, p3x, p3y; - var perpx, perpy, perp2x, perp2y, perp3x, perp3y; - var a1, b1, c1, a2, b2, c2; - var denom, pdist, dist; - - p1x = points[0]; - p1y = points[1]; - - p2x = points[2]; - p2y = points[3]; - - perpx = -(p1y - p2y); - perpy = p1x - p2x; - - dist = Math.sqrt(perpx*perpx + perpy*perpy); - - perpx /= dist; - perpy /= dist; - perpx *= width; - perpy *= width; - - // start - verts.push(p1x - perpx , p1y - perpy, - r, g, b, alpha); - - verts.push(p1x + perpx , p1y + perpy, - r, g, b, alpha); - - for (i = 1; i < length-1; i++) - { - p1x = points[(i-1)*2]; - p1y = points[(i-1)*2 + 1]; - - p2x = points[(i)*2]; - p2y = points[(i)*2 + 1]; - - p3x = points[(i+1)*2]; - p3y = points[(i+1)*2 + 1]; - - perpx = -(p1y - p2y); - perpy = p1x - p2x; - - dist = Math.sqrt(perpx*perpx + perpy*perpy); - perpx /= dist; - perpy /= dist; - perpx *= width; - perpy *= width; - - perp2x = -(p2y - p3y); - perp2y = p2x - p3x; - - dist = Math.sqrt(perp2x*perp2x + perp2y*perp2y); - perp2x /= dist; - perp2y /= dist; - perp2x *= width; - perp2y *= width; - - a1 = (-perpy + p1y) - (-perpy + p2y); - b1 = (-perpx + p2x) - (-perpx + p1x); - c1 = (-perpx + p1x) * (-perpy + p2y) - (-perpx + p2x) * (-perpy + p1y); - a2 = (-perp2y + p3y) - (-perp2y + p2y); - b2 = (-perp2x + p2x) - (-perp2x + p3x); - c2 = (-perp2x + p3x) * (-perp2y + p2y) - (-perp2x + p2x) * (-perp2y + p3y); - - denom = a1*b2 - a2*b1; - - if (Math.abs(denom) < 0.1 ) - { - - denom+=10.1; - verts.push(p2x - perpx , p2y - perpy, - r, g, b, alpha); - - verts.push(p2x + perpx , p2y + perpy, - r, g, b, alpha); - - continue; - } - - px = (b1*c2 - b2*c1)/denom; - py = (a2*c1 - a1*c2)/denom; - - - pdist = (px -p2x) * (px -p2x) + (py -p2y) + (py -p2y); - - - if (pdist > 140 * 140) - { - perp3x = perpx - perp2x; - perp3y = perpy - perp2y; - - dist = Math.sqrt(perp3x*perp3x + perp3y*perp3y); - perp3x /= dist; - perp3y /= dist; - perp3x *= width; - perp3y *= width; - - verts.push(p2x - perp3x, p2y -perp3y); - verts.push(r, g, b, alpha); - - verts.push(p2x + perp3x, p2y +perp3y); - verts.push(r, g, b, alpha); - - verts.push(p2x - perp3x, p2y -perp3y); - verts.push(r, g, b, alpha); - - indexCount++; - } - else - { - - verts.push(px , py); - verts.push(r, g, b, alpha); - - verts.push(p2x - (px-p2x), p2y - (py - p2y)); - verts.push(r, g, b, alpha); - } - } - - p1x = points[(length-2)*2]; - p1y = points[(length-2)*2 + 1]; - - p2x = points[(length-1)*2]; - p2y = points[(length-1)*2 + 1]; - - perpx = -(p1y - p2y); - perpy = p1x - p2x; - - dist = Math.sqrt(perpx*perpx + perpy*perpy); - perpx /= dist; - perpy /= dist; - perpx *= width; - perpy *= width; - - verts.push(p2x - perpx , p2y - perpy); - verts.push(r, g, b, alpha); - - verts.push(p2x + perpx , p2y + perpy); - verts.push(r, g, b, alpha); - - indices.push(indexStart); - - for (i = 0; i < indexCount; i++) - { - indices.push(indexStart++); - } - - indices.push(indexStart-1); -}; - -/** - * Builds a complex polygon to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildComplexPoly = function (graphicsData, webGLData) -{ - //TODO - no need to copy this as it gets turned into a FLoat32Array anyways.. - var points = graphicsData.points.slice(); - - if (points.length < 6) - { - return; - } - - // get first and last point.. figure out the middle! - var indices = webGLData.indices; - webGLData.points = points; - webGLData.alpha = graphicsData.fillAlpha; - webGLData.color = utils.hex2rgb(graphicsData.fillColor); - - // calclate the bounds.. - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - var x,y; - - // get size.. - for (var i = 0; i < points.length; i+=2) - { - x = points[i]; - y = points[i+1]; - - minX = x < minX ? x : minX; - maxX = x > maxX ? x : maxX; - - minY = y < minY ? y : minY; - maxY = y > maxY ? y : maxY; - } - - // add a quad to the end cos there is no point making another buffer! - points.push(minX, minY, - maxX, minY, - maxX, maxY, - minX, maxY); - - // push a quad onto the end.. - - //TODO - this aint needed! - var length = points.length / 2; - for (i = 0; i < length; i++) - { - indices.push( i ); - } - -}; - -/** - * Builds a polygon to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildPoly = function (graphicsData, webGLData) -{ - var points = graphicsData.points; - - if (points.length < 6) - { - return; - } - - // get first and last point.. figure out the middle! - var verts = webGLData.points; - var indices = webGLData.indices; - - var length = points.length / 2; - - // sort color - var color = utils.hex2rgb(graphicsData.fillColor); - var alpha = graphicsData.fillAlpha; - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var triangles = utils.PolyK.Triangulate(points); - - if (!triangles) { - return false; - } - - var vertPos = verts.length / 6; - - var i = 0; - - for (i = 0; i < triangles.length; i+=3) - { - indices.push(triangles[i] + vertPos); - indices.push(triangles[i] + vertPos); - indices.push(triangles[i+1] + vertPos); - indices.push(triangles[i+2] +vertPos); - indices.push(triangles[i+2] + vertPos); - } - - for (i = 0; i < length; i++) - { - verts.push(points[i * 2], points[i * 2 + 1], - r, g, b, alpha); - } - - return true; -}; - -GraphicsRenderer.graphicsDataPool = []; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js new file mode 100644 index 0000000..2a48af4 --- /dev/null +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -0,0 +1,44 @@ +/** + * + * @class + * @private + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this object renderer works for. + */ +function ObjectRenderer(renderer) +{ + /** + * The renderer used by this object renderer. + * + * @member {WebGLRenderer} + */ + this.renderer = renderer; +} + +ObjectRenderer.prototype.constructor = ObjectRenderer; +module.exports = ObjectRenderer; + +ObjectRenderer.prototype.start = function () +{ + // set the shader.. +} + +ObjectRenderer.prototype.stop = function () +{ + // flush! +} + +ObjectRenderer.prototype.flush = function () +{ + // flush! +} + +ObjectRenderer.prototype.render = function (/* object */) +{ + // render the object +} + +ObjectRenderer.prototype.destroy = function () +{ + this.renderer = null; +} diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 0669e79..c2294f9 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,7 +500,7 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.spriteRenderer.flush(); + renderer.objectRenderers.sprite.flush(); renderer.filterManager.pushFilter(this._filterBlock); } diff --git a/src/core/display/Sprite.js b/src/core/display/Sprite.js deleted file mode 100644 index e4e9d01..0000000 --- a/src/core/display/Sprite.js +++ /dev/null @@ -1,461 +0,0 @@ -var math = require('../math'), - Texture = require('../textures/Texture'), - DisplayObjectContainer = require('./DisplayObjectContainer'), - CanvasTinter = require('../renderers/canvas/utils/CanvasTinter'), - utils = require('../utils'), - CONST = require('../const'); - -/** - * The Sprite object is the base for all textured objects that are rendered to the screen - * - * A sprite can be created directly from an image like this: - * - * ```js - * var sprite = new Sprite.fromImage('assets/image.png'); - * ``` - * - * @class Sprite - * @extends DisplayObjectContainer - * @namespace PIXI - * @param texture {Texture} The texture for this sprite - */ -function Sprite(texture) -{ - DisplayObjectContainer.call(this); - - /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting than anchor to 0.5,0.5 means the textures origin is centered - * Setting the anchor to 1,1 would mean the textures origin points will be the bottom right corner - * - * @member {Point} - */ - this.anchor = new math.Point(); - - /** - * The texture that the sprite is using - * - * @member {Texture} - * @private - */ - this._texture = null; - - /** - * The width of the sprite (this is initially set by the texture) - * - * @member {number} - * @private - */ - this._width = 0; - - /** - * The height of the sprite (this is initially set by the texture) - * - * @member {number} - * @private - */ - this._height = 0; - - /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the sprite. Set to CONST.blendModes.NORMAL to remove any blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. - * - * @member {AbstractFilter} - */ - this.shader = null; - - this.renderable = true; - - // call texture setter - this.texture = texture || Texture.EMPTY; -} - -Sprite.prototype.destroy = function (destroyTexture, destroyBaseTexture) -{ - DisplayObjectContainer.prototype.destroy.call(this); - - this.anchor = null; - - if (destroyTexture) - { - this._texture.destroy(destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// constructor -Sprite.prototype = Object.create(DisplayObjectContainer.prototype); -Sprite.prototype.constructor = Sprite; -module.exports = Sprite; - -Object.defineProperties(Sprite.prototype, { - /** - * The width of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - width: { - get: function () - { - return this.scale.x * this.texture.frame.width; - }, - set: function (value) - { - this.scale.x = value / this.texture.frame.width; - this._width = value; - } - }, - - /** - * The height of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - height: { - get: function () - { - return this.scale.y * this.texture.frame.height; - }, - set: function (value) - { - this.scale.y = value / this.texture.frame.height; - this._height = value; - } - }, - - /** - * The height of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - texture: { - get: function () - { - return this._texture; - }, - set: function (value) - { - if (this._texture === value) - { - return; - } - - this._texture = value; - this.cachedTint = 0xFFFFFF; - - if (value) - { - // wait for the texture to load - if (value.baseTexture.hasLoaded) - { - this._onTextureUpdate(); - } - else - { - value.once('update', this._onTextureUpdate.bind(this)); - } - } - } - }, -}); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = this._width / this.texture.frame.width; - } - - if (this._height) - { - this.scale.y = this._height / this.texture.frame.height; - } -}; - -Sprite.prototype._renderWebGL = function (renderer) -{ - - // this is where content gets renderd.. - // watch this space for a little render state manager.. - renderer.setObjectRenderer(renderer.spriteRenderer); - renderer.spriteRenderer.render(this); -}; - -/** - * Returns the bounds of the Sprite as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param matrix {Matrix} the transformation matrix of the sprite - * @return {Rectangle} the framing rectangle - */ -Sprite.prototype.getBounds = function (matrix) -{ - var width = this.texture.frame.width; - var height = this.texture.frame.height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = matrix || this.worldTransform ; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var minX, - maxX, - minY, - maxY; - - if(b === 0 && c === 0) - { - // scale may be negative! - if (a < 0) - { - a *= -1; - } - - if (d < 0) - { - d *= -1; - } - - // this means there is no rotation going on right? RIGHT? - // if thats the case then we can avoid checking the bound values! yay - minX = a * w1 + tx; - maxX = a * w0 + tx; - minY = d * h1 + ty; - maxY = d * h0 + ty; - } - else - { - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {CanvasRenderer} The renderer -*/ -Sprite.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || this.texture.crop.width <= 0 || this.texture.crop.height <= 0) - { - return; - } - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - renderer.context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - // Ignore null sources - if (this.texture.valid) - { - var resolution = this.texture.baseTexture.resolution / renderer.resolution; - - renderer.context.globalAlpha = this.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for this texture - if (renderer.smoothProperty && renderer.scaleMode !== this.texture.baseTexture.scaleMode) - { - renderer.scaleMode = this.texture.baseTexture.scaleMode; - renderer.context[renderer.smoothProperty] = (renderer.scaleMode === CONST.scaleModes.LINEAR); - } - - // If the texture is trimmed we offset by the trim x/y, otherwise we use the frame dimensions - var dx = (this.texture.trim ? this.texture.trim.x : 0) - (this.anchor.x * this.texture.trim.width); - var dy = (this.texture.trim ? this.texture.trim.y : 0) - (this.anchor.y * this.texture.trim.height); - - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - this.worldTransform.a, - this.worldTransform.b, - this.worldTransform.c, - this.worldTransform.d, - (this.worldTransform.tx * renderer.resolution) | 0, - (this.worldTransform.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - this.worldTransform.a, - this.worldTransform.b, - this.worldTransform.c, - this.worldTransform.d, - this.worldTransform.tx * renderer.resolution, - this.worldTransform.ty * renderer.resolution - ); - } - - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - // TODO clean up caching - how to clean up the caches? - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - - renderer.context.drawImage( - this.tintedTexture, - 0, - 0, - this.texture.crop.width, - this.texture.crop.height, - dx / resolution, - dy / resolution, - this.texture.crop.width / resolution, - this.texture.crop.height / resolution - ); - } - else - { - renderer.context.drawImage( - this.texture.baseTexture.source, - this.texture.crop.x, - this.texture.crop.y, - this.texture.crop.width, - this.texture.crop.height, - dx / resolution, - dy / resolution, - this.texture.crop.width / resolution, - this.texture.crop.height / resolution - ); - } - } - - for (var i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -// some helper functions.. - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {String} The frame Id of the texture in the cache - * @return {Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache' + this); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {String} The image url of the texture - * @return {Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/display/SpriteBatch.js b/src/core/display/SpriteBatch.js deleted file mode 100644 index c196fef..0000000 --- a/src/core/display/SpriteBatch.js +++ /dev/null @@ -1,183 +0,0 @@ -var DisplayObjectContainer = require('./DisplayObjectContainer'), - WebGLFastSpriteBatch = require('../renderers/webgl/utils/WebGLFastSpriteBatch'); - -/** - * The SpriteBatch class is a really fast version of the DisplayObjectContainer built solely for speed, - * so use when you need a lot of sprites or particles. The tradeoff of the SpriteBatch is that advanced - * functionality will not work. SpriteBatch implements only the basic object transform (position, scale, rotation). - * Any other functionality like tinting, masking, etc will not work on sprites in this batch. - * - * It's extremely easy to use : - * - * ```js - * var container = new SpriteBatch(); - * - * for(var i = 0; i < 100; ++i) - * { - * var sprite = new PIXI.Sprite.fromImage("myImage.png"); - * container.addChild(sprite); - * } - * ``` - * - * And here you have a hundred sprites that will be renderer at the speed of light. - * - * @class - * @namespace PIXI - */ - -//TODO RENAME to PARTICLE CONTAINER? -function SpriteBatch() -{ - DisplayObjectContainer.call(this); -} - -SpriteBatch.prototype = Object.create(DisplayObjectContainer.prototype); -SpriteBatch.prototype.constructor = SpriteBatch; -module.exports = SpriteBatch; - -/** - * Updates the object transform for rendering - * - * @private - */ -SpriteBatch.prototype.updateTransform = function () -{ - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.DisplayObjectContainer.prototype.updateTransform.call( this ); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} The webgl renderer - * @private - */ -SpriteBatch.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || !this.children.length) - { - return; - } - - renderer.spriteBatch.stop(); - - renderer.shaderManager.setShader(renderer.shaderManager.fastShader); - - renderer.fastSpriteBatch.begin(this); - renderer.fastSpriteBatch.render(this); - - renderer.spriteBatch.start(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} The canvas renderer - * @private - */ -SpriteBatch.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || !this.children.length) - { - return; - } - - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - var frame = child.texture.frame; - - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) - { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) - { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx, - transform.ty - ); - - isRotated = false; - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5) | 0, - ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5) | 0, - frame.width * child.scale.x, - frame.height * child.scale.y - ); - } - else - { - if (!isRotated) - { - isRotated = true; - } - - child.displayObjectUpdateTransform(); - - var childTransform = child.worldTransform; - - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx | 0, - childTransform.ty | 0 - ); - } - else - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx, - childTransform.ty - ); - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - ((child.anchor.x) * (-frame.width) + 0.5) | 0, - ((child.anchor.y) * (-frame.height) + 0.5) | 0, - frame.width, - frame.height - ); - } - } -}; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js index 70d28b1..baab0f8 100644 --- a/src/core/primitives/Graphics.js +++ b/src/core/primitives/Graphics.js @@ -738,17 +738,17 @@ return; } - + */ - + if (this.glDirty) { this.dirty = true; this.glDirty = false; } - renderer.setObjectRenderer(renderer.graphicsRenderer); - renderer.graphicsRenderer.render(this); + renderer.setObjectRenderer(renderer.objectRenderers.graphics); + renderer.objectRenderers.graphics.render(this); }; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js new file mode 100644 index 0000000..55dc7a5 --- /dev/null +++ b/src/core/primitives/GraphicsRenderer.js @@ -0,0 +1,874 @@ +var utils = require('../../../utils'), + math = require('../../../math'), + CONST = require('../../../const'), + ObjectRenderer = require('../renderers/webgl/utils/ObjectRenderer'), + WebGLRenderer = require('../renderers/webgl/WebGLRenderer'), + WebGLGraphicsData = require('./WebGLGraphicsData'); + +/** + * Renders the graphics object. + * + * @class + * @private + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this object renderer works for. + */ +function GraphicsRenderer(renderer) +{ + ObjectRenderer.call(this, renderer); + + this.graphicsDataPool = []; +} + +GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); +GraphicsRenderer.prototype.constructor = GraphicsRenderer; +module.exports = GraphicsRenderer; + +WebGLRenderer.registerObjectRenderer('graphics', GraphicsRenderer); + +/** + * Destroys this renderer. + * + */ +GraphicsRenderer.prototype.destroy = function () { + ObjectRenderer.prototype.destroy.call(this); + + this.graphicsDataPool = null; +}; + +/** + * Renders a graphics object. + * + * @param graphics {Graphics} The graphics object to render. + */ +GraphicsRenderer.prototype.render = function(graphics) +{ + var renderer = this.renderer; + var gl = renderer.gl; + + var projection = renderer.projection, + offset = renderer.offset, + shader = renderer.shaderManager.primitiveShader, + webGLData; + + if (graphics.dirty) + { + this.updateGraphics(graphics, gl); + } + + var webGL = graphics._webGL[gl.id]; + + // This could be speeded up for sure! + + renderer.blendModeManager.setBlendMode( graphics.blendMode ); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.stencilManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.stencilManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.primitiveShader; + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniform1f(shader.uniforms.flipY._location, 1); + + gl.uniform2f(shader.uniforms.projectionVector._location, projection.x, -projection.y); + gl.uniform2f(shader.uniforms.offsetVector._location, -offset.x, -offset.y); + + gl.uniform3fv(shader.uniforms.tint._location, utils.hex2rgb(graphics.tint)); + + gl.uniform1f(shader.uniforms.alpha._location, graphics.worldAlpha); + + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.attributes.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); + gl.vertexAttribPointer(shader.attributes.aColor, 4, gl.FLOAT, false,4 * 6, 2 * 4); + + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + } + } +} + +/** + * Updates the graphics object + * + * @private + * @param graphicsData {Graphics} The graphics object to update + * @param gl {WebGLContext} the current WebGL drawing context + */ +GraphicsRenderer.prototype.updateGraphics = function(graphics) +{ + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[gl.id]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[gl.id] = {lastIndex:0, data:[], gl:gl}; + } + + // flag the graphics as not dirty as we are about to update it... + graphics.dirty = false; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty) + { + graphics.clearDirty = false; + + // lop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + graphicsData.reset(); + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + if (data.type === CONST.SHAPES.POLY) + { + // need to add the points the the graphics object.. + data.points = data.shape.points.slice(); + if (data.shape.closed) + { + // close the poly if the value is true! + if (data.points[0] !== data.points[data.points.length-2] || data.points[1] !== data.points[data.points.length-1]) + { + data.points.push(data.points[0], data.points[1]); + } + } + + // MAKE SURE WE HAVE THE CORRECT TYPE.. + if (data.fill) + { + if (data.points.length >= 6) + { + if (data.points.length < 6 * 2) + { + webGLData = this.switchMode(webGL, 0); + + var canDrawUsingSimple = this.buildPoly(data, webGLData); + // console.log(canDrawUsingSimple); + + if (!canDrawUsingSimple) + { + // console.log("<>>>") + webGLData = this.switchMode(webGL, 1); + this.buildComplexPoly(data, webGLData); + } + + } + else + { + webGLData = this.switchMode(webGL, 1); + this.buildComplexPoly(data, webGLData); + } + } + } + + if (data.lineWidth > 0) + { + webGLData = this.switchMode(webGL, 0); + this.buildLine(data, webGLData); + } + } + else + { + webGLData = this.switchMode(webGL, 0); + + if (data.type === CONST.SHAPES.RECT) + { + this.buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + this.buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + this.buildRoundedRectangle(data, webGLData); + } + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } +} + +/** + * + * + * @private + * @param webGL {WebGLContext} + * @param type {number} + */ +GraphicsRenderer.prototype.switchMode = function (webGL, type) +{ + var webGLData; + + if (!webGL.data.length) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(webGL.gl); + webGLData.mode = type; + webGL.data.push(webGLData); + } + else + { + webGLData = webGL.data[webGL.data.length-1]; + + if (webGLData.mode !== type || type === 1) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(webGL.gl); + webGLData.mode = type; + webGL.data.push(webGLData); + } + } + + webGLData.dirty = true; + + return webGLData; +}; + +/** + * Builds a rectangle to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildRectangle = function (graphicsData, webGLData) +{ + // --- // + // need to convert points to a nice regular data + // + var rectData = graphicsData.shape; + var x = rectData.x; + var y = rectData.y; + var width = rectData.width; + var height = rectData.height; + + if (graphicsData.fill) + { + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var verts = webGLData.points; + var indices = webGLData.indices; + + var vertPos = verts.length/6; + + // start + verts.push(x, y); + verts.push(r, g, b, alpha); + + verts.push(x + width, y); + verts.push(r, g, b, alpha); + + verts.push(x , y + height); + verts.push(r, g, b, alpha); + + verts.push(x + width, y + height); + verts.push(r, g, b, alpha); + + // insert 2 dead triangles.. + indices.push(vertPos, vertPos, vertPos+1, vertPos+2, vertPos+3, vertPos+3); + } + + if (graphicsData.lineWidth) + { + var tempPoints = graphicsData.points; + + graphicsData.points = [x, y, + x + width, y, + x + width, y + height, + x, y + height, + x, y]; + + + this.buildLine(graphicsData, webGLData); + + graphicsData.points = tempPoints; + } +}; + +/** + * Builds a rounded rectangle to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildRoundedRectangle = function (graphicsData, webGLData) +{ + var rrectData = graphicsData.shape; + var x = rrectData.x; + var y = rrectData.y; + var width = rrectData.width; + var height = rrectData.height; + + var radius = rrectData.radius; + + var recPoints = []; + recPoints.push(x, y + radius); + recPoints = recPoints.concat(this.quadraticBezierCurve(x, y + height - radius, x, y + height, x + radius, y + height)); + recPoints = recPoints.concat(this.quadraticBezierCurve(x + width - radius, y + height, x + width, y + height, x + width, y + height - radius)); + recPoints = recPoints.concat(this.quadraticBezierCurve(x + width, y + radius, x + width, y, x + width - radius, y)); + recPoints = recPoints.concat(this.quadraticBezierCurve(x + radius, y, x, y, x, y + radius)); + + if (graphicsData.fill) + { + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var verts = webGLData.points; + var indices = webGLData.indices; + + var vecPos = verts.length/6; + + var triangles = utils.PolyK.Triangulate(recPoints); + + // + + var i = 0; + for (i = 0; i < triangles.length; i+=3) + { + indices.push(triangles[i] + vecPos); + indices.push(triangles[i] + vecPos); + indices.push(triangles[i+1] + vecPos); + indices.push(triangles[i+2] + vecPos); + indices.push(triangles[i+2] + vecPos); + } + + for (i = 0; i < recPoints.length; i++) + { + verts.push(recPoints[i], recPoints[++i], r, g, b, alpha); + } + } + + if (graphicsData.lineWidth) + { + var tempPoints = graphicsData.points; + + graphicsData.points = recPoints; + + this.buildLine(graphicsData, webGLData); + + graphicsData.points = tempPoints; + } +}; + +/** + * Calculate the points for a quadratic bezier curve. (helper function..) + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @private + * @param fromX {number} Origin point x + * @param fromY {number} Origin point x + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {number[]} + */ +GraphicsRenderer.prototype.quadraticBezierCurve = function (fromX, fromY, cpX, cpY, toX, toY) +{ + + var xa, + ya, + xb, + yb, + x, + y, + n = 20, + points = []; + + function getPt(n1 , n2, perc) { + var diff = n2 - n1; + + return n1 + ( diff * perc ); + } + + var j = 0; + for (var i = 0; i <= n; i++ ) { + j = i / n; + + // The Green Line + xa = getPt( fromX , cpX , j ); + ya = getPt( fromY , cpY , j ); + xb = getPt( cpX , toX , j ); + yb = getPt( cpY , toY , j ); + + // The Black Dot + x = getPt( xa , xb , j ); + y = getPt( ya , yb , j ); + + points.push(x, y); + } + return points; +}; + +/** + * Builds a circle to draw + * + * @private + * @param graphicsData {Graphics} The graphics object to draw + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildCircle = function (graphicsData, webGLData) +{ + // need to convert points to a nice regular data + var circleData = graphicsData.shape; + var x = circleData.x; + var y = circleData.y; + var width; + var height; + + // TODO - bit hacky?? + if (graphicsData.type === CONST.SHAPES.CIRC) + { + width = circleData.radius; + height = circleData.radius; + } + else + { + width = circleData.width; + height = circleData.height; + } + + var totalSegs = 40; + var seg = (Math.PI * 2) / totalSegs ; + + var i = 0; + + if (graphicsData.fill) + { + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var verts = webGLData.points; + var indices = webGLData.indices; + + var vecPos = verts.length/6; + + indices.push(vecPos); + + for (i = 0; i < totalSegs + 1 ; i++) + { + verts.push(x,y, r, g, b, alpha); + + verts.push(x + Math.sin(seg * i) * width, + y + Math.cos(seg * i) * height, + r, g, b, alpha); + + indices.push(vecPos++, vecPos++); + } + + indices.push(vecPos-1); + } + + if (graphicsData.lineWidth) + { + var tempPoints = graphicsData.points; + + graphicsData.points = []; + + for (i = 0; i < totalSegs + 1; i++) + { + graphicsData.points.push(x + Math.sin(seg * i) * width, + y + Math.cos(seg * i) * height); + } + + this.buildLine(graphicsData, webGLData); + + graphicsData.points = tempPoints; + } +}; + +/** + * Builds a line to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildLine = function (graphicsData, webGLData) +{ + // TODO OPTIMISE! + var i = 0; + var points = graphicsData.points; + + if (points.length === 0) + { + return; + } + + // if the line width is an odd number add 0.5 to align to a whole pixel + if (graphicsData.lineWidth%2) + { + for (i = 0; i < points.length; i++) + { + points[i] += 0.5; + } + } + + // get first and last point.. figure out the middle! + var firstPoint = new math.Point(points[0], points[1]); + var lastPoint = new math.Point(points[points.length - 2], points[points.length - 1]); + + // if the first point is the last point - gonna have issues :) + if (firstPoint.x === lastPoint.x && firstPoint.y === lastPoint.y) + { + // need to clone as we are going to slightly modify the shape.. + points = points.slice(); + + points.pop(); + points.pop(); + + lastPoint = new math.Point(points[points.length - 2], points[points.length - 1]); + + var midPointX = lastPoint.x + (firstPoint.x - lastPoint.x) *0.5; + var midPointY = lastPoint.y + (firstPoint.y - lastPoint.y) *0.5; + + points.unshift(midPointX, midPointY); + points.push(midPointX, midPointY); + } + + var verts = webGLData.points; + var indices = webGLData.indices; + var length = points.length / 2; + var indexCount = points.length; + var indexStart = verts.length/6; + + // DRAW the Line + var width = graphicsData.lineWidth / 2; + + // sort color + var color = utils.hex2rgb(graphicsData.lineColor); + var alpha = graphicsData.lineAlpha; + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var px, py, p1x, p1y, p2x, p2y, p3x, p3y; + var perpx, perpy, perp2x, perp2y, perp3x, perp3y; + var a1, b1, c1, a2, b2, c2; + var denom, pdist, dist; + + p1x = points[0]; + p1y = points[1]; + + p2x = points[2]; + p2y = points[3]; + + perpx = -(p1y - p2y); + perpy = p1x - p2x; + + dist = Math.sqrt(perpx*perpx + perpy*perpy); + + perpx /= dist; + perpy /= dist; + perpx *= width; + perpy *= width; + + // start + verts.push(p1x - perpx , p1y - perpy, + r, g, b, alpha); + + verts.push(p1x + perpx , p1y + perpy, + r, g, b, alpha); + + for (i = 1; i < length-1; i++) + { + p1x = points[(i-1)*2]; + p1y = points[(i-1)*2 + 1]; + + p2x = points[(i)*2]; + p2y = points[(i)*2 + 1]; + + p3x = points[(i+1)*2]; + p3y = points[(i+1)*2 + 1]; + + perpx = -(p1y - p2y); + perpy = p1x - p2x; + + dist = Math.sqrt(perpx*perpx + perpy*perpy); + perpx /= dist; + perpy /= dist; + perpx *= width; + perpy *= width; + + perp2x = -(p2y - p3y); + perp2y = p2x - p3x; + + dist = Math.sqrt(perp2x*perp2x + perp2y*perp2y); + perp2x /= dist; + perp2y /= dist; + perp2x *= width; + perp2y *= width; + + a1 = (-perpy + p1y) - (-perpy + p2y); + b1 = (-perpx + p2x) - (-perpx + p1x); + c1 = (-perpx + p1x) * (-perpy + p2y) - (-perpx + p2x) * (-perpy + p1y); + a2 = (-perp2y + p3y) - (-perp2y + p2y); + b2 = (-perp2x + p2x) - (-perp2x + p3x); + c2 = (-perp2x + p3x) * (-perp2y + p2y) - (-perp2x + p2x) * (-perp2y + p3y); + + denom = a1*b2 - a2*b1; + + if (Math.abs(denom) < 0.1 ) + { + + denom+=10.1; + verts.push(p2x - perpx , p2y - perpy, + r, g, b, alpha); + + verts.push(p2x + perpx , p2y + perpy, + r, g, b, alpha); + + continue; + } + + px = (b1*c2 - b2*c1)/denom; + py = (a2*c1 - a1*c2)/denom; + + + pdist = (px -p2x) * (px -p2x) + (py -p2y) + (py -p2y); + + + if (pdist > 140 * 140) + { + perp3x = perpx - perp2x; + perp3y = perpy - perp2y; + + dist = Math.sqrt(perp3x*perp3x + perp3y*perp3y); + perp3x /= dist; + perp3y /= dist; + perp3x *= width; + perp3y *= width; + + verts.push(p2x - perp3x, p2y -perp3y); + verts.push(r, g, b, alpha); + + verts.push(p2x + perp3x, p2y +perp3y); + verts.push(r, g, b, alpha); + + verts.push(p2x - perp3x, p2y -perp3y); + verts.push(r, g, b, alpha); + + indexCount++; + } + else + { + + verts.push(px , py); + verts.push(r, g, b, alpha); + + verts.push(p2x - (px-p2x), p2y - (py - p2y)); + verts.push(r, g, b, alpha); + } + } + + p1x = points[(length-2)*2]; + p1y = points[(length-2)*2 + 1]; + + p2x = points[(length-1)*2]; + p2y = points[(length-1)*2 + 1]; + + perpx = -(p1y - p2y); + perpy = p1x - p2x; + + dist = Math.sqrt(perpx*perpx + perpy*perpy); + perpx /= dist; + perpy /= dist; + perpx *= width; + perpy *= width; + + verts.push(p2x - perpx , p2y - perpy); + verts.push(r, g, b, alpha); + + verts.push(p2x + perpx , p2y + perpy); + verts.push(r, g, b, alpha); + + indices.push(indexStart); + + for (i = 0; i < indexCount; i++) + { + indices.push(indexStart++); + } + + indices.push(indexStart-1); +}; + +/** + * Builds a complex polygon to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildComplexPoly = function (graphicsData, webGLData) +{ + //TODO - no need to copy this as it gets turned into a FLoat32Array anyways.. + var points = graphicsData.points.slice(); + + if (points.length < 6) + { + return; + } + + // get first and last point.. figure out the middle! + var indices = webGLData.indices; + webGLData.points = points; + webGLData.alpha = graphicsData.fillAlpha; + webGLData.color = utils.hex2rgb(graphicsData.fillColor); + + // calclate the bounds.. + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + var x,y; + + // get size.. + for (var i = 0; i < points.length; i+=2) + { + x = points[i]; + y = points[i+1]; + + minX = x < minX ? x : minX; + maxX = x > maxX ? x : maxX; + + minY = y < minY ? y : minY; + maxY = y > maxY ? y : maxY; + } + + // add a quad to the end cos there is no point making another buffer! + points.push(minX, minY, + maxX, minY, + maxX, maxY, + minX, maxY); + + // push a quad onto the end.. + + //TODO - this aint needed! + var length = points.length / 2; + for (i = 0; i < length; i++) + { + indices.push( i ); + } + +}; + +/** + * Builds a polygon to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildPoly = function (graphicsData, webGLData) +{ + var points = graphicsData.points; + + if (points.length < 6) + { + return; + } + + // get first and last point.. figure out the middle! + var verts = webGLData.points; + var indices = webGLData.indices; + + var length = points.length / 2; + + // sort color + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var triangles = utils.PolyK.Triangulate(points); + + if (!triangles) { + return false; + } + + var vertPos = verts.length / 6; + + var i = 0; + + for (i = 0; i < triangles.length; i+=3) + { + indices.push(triangles[i] + vertPos); + indices.push(triangles[i] + vertPos); + indices.push(triangles[i+1] + vertPos); + indices.push(triangles[i+2] +vertPos); + indices.push(triangles[i+2] + vertPos); + } + + for (i = 0; i < length; i++) + { + verts.push(points[i * 2], points[i * 2 + 1], + r, g, b, alpha); + } + + return true; +}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 547bdc7..b0189c5 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,6 +1,4 @@ -var SpriteRenderer = require('./utils/SpriteRenderer'), - GraphicsRenderer = require('./utils/GraphicsRenderer'), - WebGLFastSpriteBatch = require('./utils/WebGLFastSpriteBatch'), +var WebGLFastSpriteBatch = require('./utils/WebGLFastSpriteBatch'), WebGLShaderManager = require('./managers/WebGLShaderManager'), WebGLMaskManager = require('./managers/WebGLMaskManager'), WebGLFilterManager = require('./managers/WebGLFilterManager'), @@ -28,21 +26,21 @@ * @param [options.preserveDrawingBuffer=false] {boolean} enables drawing buffer preservation, enable this if you need to call toDataUrl on the webgl context * @param [options.resolution=1] {number} the resolution of the renderer retina would be 2 */ -function WebGLRenderer(width, height, options) +function WebGLRenderer(width, height, options) { utils.sayHello('webGL'); - if (options) + if (options) { - for (var i in CONST.defaultRenderOptions) + for (var i in CONST.defaultRenderOptions) { - if (typeof options[i] === 'undefined') + if (typeof options[i] === 'undefined') { options[i] = CONST.defaultRenderOptions[i]; } } } - else + else { options = CONST.defaultRenderOptions; } @@ -191,14 +189,6 @@ /** * Manages the rendering of sprites - * @member {SpriteRenderer} - */ - this.spriteRenderer = new SpriteRenderer(this); - - this.graphicsRenderer = new GraphicsRenderer(this); - - /** - * Manages the rendering of sprites * @member {WebGLFastSpriteBatch} */ this.fastSpriteBatch = new WebGLFastSpriteBatch(this); @@ -245,8 +235,17 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - this.currentRenderer = this.spriteRenderer; + /** + * Manages the renderer of specific objects. + * + * @member {object} + */ + this.objectRenderers = {}; + // create an instance of each registered object renderer + for (var o in WebGLRenderer._objectRenderers) { + this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); + } } // constructor @@ -255,21 +254,20 @@ utils.eventTarget.mixin(WebGLRenderer.prototype); -Object.defineProperties(WebGLRenderer.prototype, -{ +Object.defineProperties(WebGLRenderer.prototype, { /** * The background color to fill if not transparent * * @member {number} * @memberof WebGLRenderer# */ - backgroundColor: + backgroundColor: { - get: function () + get: function () { return this._backgroundColor; }, - set: function (val) + set: function (val) { this._backgroundColor = val; utils.hex2rgb(val, this._backgroundColorRgb); @@ -281,12 +279,12 @@ * * @private */ -WebGLRenderer.prototype._initContext = function () +WebGLRenderer.prototype._initContext = function () { var gl = this.view.getContext('webgl', this._contextOptions) || this.view.getContext('experimental-webgl', this._contextOptions); this.gl = gl; - if (!gl) + if (!gl) { // fail, not able to get a context throw new Error('This browser does not support webGL. Try using the canvas renderer'); @@ -312,10 +310,10 @@ * * @param object {DisplayObject} the object to be rendered */ -WebGLRenderer.prototype.render = function (object) +WebGLRenderer.prototype.render = function (object) { // no point rendering if our context has been blown up! - if (this.gl.isContextLost()) + if (this.gl.isContextLost()) { return; } @@ -336,13 +334,13 @@ // make sure we are bound to the main frame buffer gl.bindFramebuffer(gl.FRAMEBUFFER, null); - if (this.clearBeforeRender) + if (this.clearBeforeRender) { - if (this.transparent) + if (this.transparent) { gl.clearColor(0, 0, 0, 0); } - else + else { gl.clearColor(this._backgroundColorRgb[0], this._backgroundColorRgb[1], this._backgroundColorRgb[2], 1); } @@ -360,7 +358,7 @@ * @param projection {Point} The projection * @param buffer {Array} a standard WebGL buffer */ -WebGLRenderer.prototype.renderDisplayObject = function (displayObject, projection, buffer) +WebGLRenderer.prototype.renderDisplayObject = function (displayObject, projection, buffer) { this.blendModeManager.setBlendMode(CONST.blendModes.NORMAL); @@ -386,9 +384,9 @@ this.currentRenderer.flush(); }; -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) +WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) { - if(this.currentRenderer === objectRenderer) + if (this.currentRenderer === objectRenderer) { return; } @@ -396,7 +394,7 @@ this.currentRenderer.stop(); this.currentRenderer = objectRenderer; this.currentRenderer.start(); -} +}; /** * Resizes the webGL view to the specified width and height. @@ -404,7 +402,7 @@ * @param width {number} the new width of the webGL view * @param height {number} the new height of the webGL view */ -WebGLRenderer.prototype.resize = function (width, height) +WebGLRenderer.prototype.resize = function (width, height) { this.width = width * this.resolution; this.height = height * this.resolution; @@ -412,7 +410,7 @@ this.view.width = this.width; this.view.height = this.height; - if (this.autoResize) + if (this.autoResize) { this.view.style.width = this.width / this.resolution + 'px'; this.view.style.height = this.height / this.resolution + 'px'; @@ -429,18 +427,18 @@ * * @param texture {BaseTexture|Texture} the texture to update */ -WebGLRenderer.prototype.updateTexture = function (texture) +WebGLRenderer.prototype.updateTexture = function (texture) { texture = texture.baseTexture || texture; - if (!texture.hasLoaded) + if (!texture.hasLoaded) { return; } var gl = this.gl; - if (!texture._glTextures[gl.id]) + if (!texture._glTextures[gl.id]) { texture._glTextures[gl.id] = gl.createTexture(); texture.on('update', this._boundUpdateTexture); @@ -455,22 +453,22 @@ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, texture.scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); - if (texture.mipmap && utils.isPowerOfTwo(texture.width, texture.height)) + if (texture.mipmap && utils.isPowerOfTwo(texture.width, texture.height)) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); gl.generateMipmap(gl.TEXTURE_2D); } - else + else { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); } - if (!texture._powerOf2) + if (!texture._powerOf2) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); } - else + else { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); @@ -479,16 +477,16 @@ return texture._glTextures[gl.id]; }; -WebGLRenderer.prototype.destroyTexture = function (texture) +WebGLRenderer.prototype.destroyTexture = function (texture) { texture = texture.baseTexture || texture; - if (!texture.hasLoaded) + if (!texture.hasLoaded) { return; } - if (texture._glTextures[this.gl.id]) + if (texture._glTextures[this.gl.id]) { this.gl.deleteTexture(texture._glTextures[this.gl.id]); } @@ -500,7 +498,7 @@ * @param event {Event} * @private */ -WebGLRenderer.prototype.handleContextLost = function (event) +WebGLRenderer.prototype.handleContextLost = function (event) { event.preventDefault(); }; @@ -511,12 +509,12 @@ * @param event {Event} * @private */ -WebGLRenderer.prototype.handleContextRestored = function () +WebGLRenderer.prototype.handleContextRestored = function () { this._initContext(); // empty all the ol gl textures as they are useless now - for (var key in utils.TextureCache) + for (var key in utils.TextureCache) { var texture = utils.TextureCache[key].baseTexture; texture._glTextures = []; @@ -528,9 +526,9 @@ * * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ -WebGLRenderer.prototype.destroy = function (removeView) +WebGLRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parent) + if (removeView && this.view.parent) { this.view.parent.removeChild(this.view); } @@ -541,10 +539,15 @@ // time to create the render managers! each one focuses on managine a state in webGL this.shaderManager.destroy(); - this.spriteRenderer.destroy(); this.maskManager.destroy(); this.filterManager.destroy(); + for (var o in this.objectRenderers) { + this.objectRenderers[o].destroy(); + this.objectRenderers[o] = null; + } + + this.objectRenderers = null; // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -574,7 +577,6 @@ this.drawCount = 0; this.shaderManager = null; - this.spriteRenderer = null; this.maskManager = null; this.filterManager = null; this.stencilManager = null; @@ -591,11 +593,11 @@ * * @private */ -WebGLRenderer.prototype._mapBlendModes = function () +WebGLRenderer.prototype._mapBlendModes = function () { var gl = this.gl; - if (!this.blendModes) + if (!this.blendModes) { this.blendModes = {}; @@ -620,3 +622,9 @@ }; WebGLRenderer.glContextId = 0; +WebGLRenderer._objectRenderers = {}; + +WebGLRenderer.registerObjectRenderer = function (name, ctor) { + WebGLRenderer._objectRenderers[name] = ctor; +}; + diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js index 773cc5d..e81ca56 100644 --- a/src/core/renderers/webgl/managers/WebGLMaskManager.js +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -20,11 +20,11 @@ */ WebGLMaskManager.prototype.pushMask = function (maskData) { - this.renderer.setObjectRenderer( this.renderer.graphicsRenderer ); + this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); if (maskData.dirty) { - this.renderer.graphicsRenderer.updateGraphics(maskData, this.renderer.gl); + this.renderer.objectRenderers.graphics.updateGraphics(maskData, this.renderer.gl); } if (!maskData._webGL[this.renderer.gl.id].data.length) @@ -42,7 +42,7 @@ */ WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.setObjectRenderer( this.renderer.graphicsRenderer ); - + this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.stencilManager.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); }; diff --git a/src/core/renderers/webgl/utils/GraphicsRenderer.js b/src/core/renderers/webgl/utils/GraphicsRenderer.js deleted file mode 100644 index 6713275..0000000 --- a/src/core/renderers/webgl/utils/GraphicsRenderer.js +++ /dev/null @@ -1,894 +0,0 @@ -var utils = require('../../../utils'), - math = require('../../../math'), - CONST = require('../../../const'), - WebGLGraphicsData = require('./WebGLGraphicsData'); - -/** - * A set of functions used by the webGL renderer to draw the primitive graphics data - * - * @namespace PIXI - * @private - */ -var WebGLGraphics = module.exports = {}; - -/** - * Renders the graphics object - * - * @static - * @private - * @param graphics {Graphics} - * @param renderer {WebGLRenderer} - */ - -var GraphicsRenderer = function(renderer) -{ - this.renderer = renderer; - this.graphicsDataPool = []; -} - -module.exports = GraphicsRenderer; - -GraphicsRenderer.prototype.start = function() -{ - // set the shader.. - -} - -GraphicsRenderer.prototype.stop = function() -{ - // flush! -} - -GraphicsRenderer.prototype.flush = function() -{ - // flush! -} - - - -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var projection = renderer.projection, - offset = renderer.offset, - shader = renderer.shaderManager.primitiveShader, - webGLData; - - if (graphics.dirty) - { - this.updateGraphics(graphics, gl); - } - - var webGL = graphics._webGL[gl.id]; - - // This could be speeded up for sure! - - renderer.blendModeManager.setBlendMode( graphics.blendMode ); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.stencilManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.stencilManager.popStencil(graphics, webGLData, renderer); - } - else - { - webGLData = webGL.data[i]; - - - renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); - shader = renderer.shaderManager.primitiveShader; - gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); - - gl.uniform1f(shader.uniforms.flipY._location, 1); - - gl.uniform2f(shader.uniforms.projectionVector._location, projection.x, -projection.y); - gl.uniform2f(shader.uniforms.offsetVector._location, -offset.x, -offset.y); - - gl.uniform3fv(shader.uniforms.tint._location, utils.hex2rgb(graphics.tint)); - - gl.uniform1f(shader.uniforms.alpha._location, graphics.worldAlpha); - - - gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); - - gl.vertexAttribPointer(shader.attributes.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); - gl.vertexAttribPointer(shader.attributes.aColor, 4, gl.FLOAT, false,4 * 6, 2 * 4); - - // set the index buffer! - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); - gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); - } - } -} - -/** - * Updates the graphics object - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object to update - * @param gl {WebGLContext} the current WebGL drawing context - */ - -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[gl.id]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[gl.id] = {lastIndex:0, data:[], gl:gl}; - } - - // flag the graphics as not dirty as we are about to update it... - graphics.dirty = false; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty) - { - graphics.clearDirty = false; - - // lop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - graphicsData.reset(); - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - if (data.type === CONST.SHAPES.POLY) - { - // need to add the points the the graphics object.. - data.points = data.shape.points.slice(); - if (data.shape.closed) - { - // close the poly if the value is true! - if (data.points[0] !== data.points[data.points.length-2] || data.points[1] !== data.points[data.points.length-1]) - { - data.points.push(data.points[0], data.points[1]); - } - } - - // MAKE SURE WE HAVE THE CORRECT TYPE.. - if (data.fill) - { - if (data.points.length >= 6) - { - if (data.points.length < 6 * 2) - { - webGLData = this.switchMode(webGL, 0); - - var canDrawUsingSimple = this.buildPoly(data, webGLData); - // console.log(canDrawUsingSimple); - - if (!canDrawUsingSimple) - { - // console.log("<>>>") - webGLData = this.switchMode(webGL, 1); - this.buildComplexPoly(data, webGLData); - } - - } - else - { - webGLData = this.switchMode(webGL, 1); - this.buildComplexPoly(data, webGLData); - } - } - } - - if (data.lineWidth > 0) - { - webGLData = this.switchMode(webGL, 0); - this.buildLine(data, webGLData); - } - } - else - { - webGLData = this.switchMode(webGL, 0); - - if (data.type === CONST.SHAPES.RECT) - { - this.buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - this.buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - this.buildRoundedRectangle(data, webGLData); - } - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -} - -/** - * @static - * @private - * @param webGL {WebGLContext} - * @param type {number} - */ -GraphicsRenderer.prototype.switchMode = function (webGL, type) -{ - var webGLData; - - if (!webGL.data.length) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(webGL.gl); - webGLData.mode = type; - webGL.data.push(webGLData); - } - else - { - webGLData = webGL.data[webGL.data.length-1]; - - if (webGLData.mode !== type || type === 1) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(webGL.gl); - webGLData.mode = type; - webGL.data.push(webGLData); - } - } - - webGLData.dirty = true; - - return webGLData; -}; - - - - - - -/** - * Builds a rectangle to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildRectangle = function (graphicsData, webGLData) -{ - // --- // - // need to convert points to a nice regular data - // - var rectData = graphicsData.shape; - var x = rectData.x; - var y = rectData.y; - var width = rectData.width; - var height = rectData.height; - - if (graphicsData.fill) - { - var color = utils.hex2rgb(graphicsData.fillColor); - var alpha = graphicsData.fillAlpha; - - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var verts = webGLData.points; - var indices = webGLData.indices; - - var vertPos = verts.length/6; - - // start - verts.push(x, y); - verts.push(r, g, b, alpha); - - verts.push(x + width, y); - verts.push(r, g, b, alpha); - - verts.push(x , y + height); - verts.push(r, g, b, alpha); - - verts.push(x + width, y + height); - verts.push(r, g, b, alpha); - - // insert 2 dead triangles.. - indices.push(vertPos, vertPos, vertPos+1, vertPos+2, vertPos+3, vertPos+3); - } - - if (graphicsData.lineWidth) - { - var tempPoints = graphicsData.points; - - graphicsData.points = [x, y, - x + width, y, - x + width, y + height, - x, y + height, - x, y]; - - - this.buildLine(graphicsData, webGLData); - - graphicsData.points = tempPoints; - } -}; - -/** - * Builds a rounded rectangle to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildRoundedRectangle = function (graphicsData, webGLData) -{ - var rrectData = graphicsData.shape; - var x = rrectData.x; - var y = rrectData.y; - var width = rrectData.width; - var height = rrectData.height; - - var radius = rrectData.radius; - - var recPoints = []; - recPoints.push(x, y + radius); - recPoints = recPoints.concat(this.quadraticBezierCurve(x, y + height - radius, x, y + height, x + radius, y + height)); - recPoints = recPoints.concat(this.quadraticBezierCurve(x + width - radius, y + height, x + width, y + height, x + width, y + height - radius)); - recPoints = recPoints.concat(this.quadraticBezierCurve(x + width, y + radius, x + width, y, x + width - radius, y)); - recPoints = recPoints.concat(this.quadraticBezierCurve(x + radius, y, x, y, x, y + radius)); - - if (graphicsData.fill) - { - var color = utils.hex2rgb(graphicsData.fillColor); - var alpha = graphicsData.fillAlpha; - - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var verts = webGLData.points; - var indices = webGLData.indices; - - var vecPos = verts.length/6; - - var triangles = utils.PolyK.Triangulate(recPoints); - - // - - var i = 0; - for (i = 0; i < triangles.length; i+=3) - { - indices.push(triangles[i] + vecPos); - indices.push(triangles[i] + vecPos); - indices.push(triangles[i+1] + vecPos); - indices.push(triangles[i+2] + vecPos); - indices.push(triangles[i+2] + vecPos); - } - - for (i = 0; i < recPoints.length; i++) - { - verts.push(recPoints[i], recPoints[++i], r, g, b, alpha); - } - } - - if (graphicsData.lineWidth) - { - var tempPoints = graphicsData.points; - - graphicsData.points = recPoints; - - this.buildLine(graphicsData, webGLData); - - graphicsData.points = tempPoints; - } -}; - -/** - * Calculate the points for a quadratic bezier curve. (helper function..) - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @static - * @private - * @param fromX {number} Origin point x - * @param fromY {number} Origin point x - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {number[]} - */ -GraphicsRenderer.prototype.quadraticBezierCurve = function (fromX, fromY, cpX, cpY, toX, toY) -{ - - var xa, - ya, - xb, - yb, - x, - y, - n = 20, - points = []; - - function getPt(n1 , n2, perc) { - var diff = n2 - n1; - - return n1 + ( diff * perc ); - } - - var j = 0; - for (var i = 0; i <= n; i++ ) { - j = i / n; - - // The Green Line - xa = getPt( fromX , cpX , j ); - ya = getPt( fromY , cpY , j ); - xb = getPt( cpX , toX , j ); - yb = getPt( cpY , toY , j ); - - // The Black Dot - x = getPt( xa , xb , j ); - y = getPt( ya , yb , j ); - - points.push(x, y); - } - return points; -}; - -/** - * Builds a circle to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object to draw - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildCircle = function (graphicsData, webGLData) -{ - // need to convert points to a nice regular data - var circleData = graphicsData.shape; - var x = circleData.x; - var y = circleData.y; - var width; - var height; - - // TODO - bit hacky?? - if (graphicsData.type === CONST.SHAPES.CIRC) - { - width = circleData.radius; - height = circleData.radius; - } - else - { - width = circleData.width; - height = circleData.height; - } - - var totalSegs = 40; - var seg = (Math.PI * 2) / totalSegs ; - - var i = 0; - - if (graphicsData.fill) - { - var color = utils.hex2rgb(graphicsData.fillColor); - var alpha = graphicsData.fillAlpha; - - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var verts = webGLData.points; - var indices = webGLData.indices; - - var vecPos = verts.length/6; - - indices.push(vecPos); - - for (i = 0; i < totalSegs + 1 ; i++) - { - verts.push(x,y, r, g, b, alpha); - - verts.push(x + Math.sin(seg * i) * width, - y + Math.cos(seg * i) * height, - r, g, b, alpha); - - indices.push(vecPos++, vecPos++); - } - - indices.push(vecPos-1); - } - - if (graphicsData.lineWidth) - { - var tempPoints = graphicsData.points; - - graphicsData.points = []; - - for (i = 0; i < totalSegs + 1; i++) - { - graphicsData.points.push(x + Math.sin(seg * i) * width, - y + Math.cos(seg * i) * height); - } - - this.buildLine(graphicsData, webGLData); - - graphicsData.points = tempPoints; - } -}; - -/** - * Builds a line to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildLine = function (graphicsData, webGLData) -{ - // TODO OPTIMISE! - var i = 0; - var points = graphicsData.points; - - if (points.length === 0) - { - return; - } - - // if the line width is an odd number add 0.5 to align to a whole pixel - if (graphicsData.lineWidth%2) - { - for (i = 0; i < points.length; i++) - { - points[i] += 0.5; - } - } - - // get first and last point.. figure out the middle! - var firstPoint = new math.Point(points[0], points[1]); - var lastPoint = new math.Point(points[points.length - 2], points[points.length - 1]); - - // if the first point is the last point - gonna have issues :) - if (firstPoint.x === lastPoint.x && firstPoint.y === lastPoint.y) - { - // need to clone as we are going to slightly modify the shape.. - points = points.slice(); - - points.pop(); - points.pop(); - - lastPoint = new math.Point(points[points.length - 2], points[points.length - 1]); - - var midPointX = lastPoint.x + (firstPoint.x - lastPoint.x) *0.5; - var midPointY = lastPoint.y + (firstPoint.y - lastPoint.y) *0.5; - - points.unshift(midPointX, midPointY); - points.push(midPointX, midPointY); - } - - var verts = webGLData.points; - var indices = webGLData.indices; - var length = points.length / 2; - var indexCount = points.length; - var indexStart = verts.length/6; - - // DRAW the Line - var width = graphicsData.lineWidth / 2; - - // sort color - var color = utils.hex2rgb(graphicsData.lineColor); - var alpha = graphicsData.lineAlpha; - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var px, py, p1x, p1y, p2x, p2y, p3x, p3y; - var perpx, perpy, perp2x, perp2y, perp3x, perp3y; - var a1, b1, c1, a2, b2, c2; - var denom, pdist, dist; - - p1x = points[0]; - p1y = points[1]; - - p2x = points[2]; - p2y = points[3]; - - perpx = -(p1y - p2y); - perpy = p1x - p2x; - - dist = Math.sqrt(perpx*perpx + perpy*perpy); - - perpx /= dist; - perpy /= dist; - perpx *= width; - perpy *= width; - - // start - verts.push(p1x - perpx , p1y - perpy, - r, g, b, alpha); - - verts.push(p1x + perpx , p1y + perpy, - r, g, b, alpha); - - for (i = 1; i < length-1; i++) - { - p1x = points[(i-1)*2]; - p1y = points[(i-1)*2 + 1]; - - p2x = points[(i)*2]; - p2y = points[(i)*2 + 1]; - - p3x = points[(i+1)*2]; - p3y = points[(i+1)*2 + 1]; - - perpx = -(p1y - p2y); - perpy = p1x - p2x; - - dist = Math.sqrt(perpx*perpx + perpy*perpy); - perpx /= dist; - perpy /= dist; - perpx *= width; - perpy *= width; - - perp2x = -(p2y - p3y); - perp2y = p2x - p3x; - - dist = Math.sqrt(perp2x*perp2x + perp2y*perp2y); - perp2x /= dist; - perp2y /= dist; - perp2x *= width; - perp2y *= width; - - a1 = (-perpy + p1y) - (-perpy + p2y); - b1 = (-perpx + p2x) - (-perpx + p1x); - c1 = (-perpx + p1x) * (-perpy + p2y) - (-perpx + p2x) * (-perpy + p1y); - a2 = (-perp2y + p3y) - (-perp2y + p2y); - b2 = (-perp2x + p2x) - (-perp2x + p3x); - c2 = (-perp2x + p3x) * (-perp2y + p2y) - (-perp2x + p2x) * (-perp2y + p3y); - - denom = a1*b2 - a2*b1; - - if (Math.abs(denom) < 0.1 ) - { - - denom+=10.1; - verts.push(p2x - perpx , p2y - perpy, - r, g, b, alpha); - - verts.push(p2x + perpx , p2y + perpy, - r, g, b, alpha); - - continue; - } - - px = (b1*c2 - b2*c1)/denom; - py = (a2*c1 - a1*c2)/denom; - - - pdist = (px -p2x) * (px -p2x) + (py -p2y) + (py -p2y); - - - if (pdist > 140 * 140) - { - perp3x = perpx - perp2x; - perp3y = perpy - perp2y; - - dist = Math.sqrt(perp3x*perp3x + perp3y*perp3y); - perp3x /= dist; - perp3y /= dist; - perp3x *= width; - perp3y *= width; - - verts.push(p2x - perp3x, p2y -perp3y); - verts.push(r, g, b, alpha); - - verts.push(p2x + perp3x, p2y +perp3y); - verts.push(r, g, b, alpha); - - verts.push(p2x - perp3x, p2y -perp3y); - verts.push(r, g, b, alpha); - - indexCount++; - } - else - { - - verts.push(px , py); - verts.push(r, g, b, alpha); - - verts.push(p2x - (px-p2x), p2y - (py - p2y)); - verts.push(r, g, b, alpha); - } - } - - p1x = points[(length-2)*2]; - p1y = points[(length-2)*2 + 1]; - - p2x = points[(length-1)*2]; - p2y = points[(length-1)*2 + 1]; - - perpx = -(p1y - p2y); - perpy = p1x - p2x; - - dist = Math.sqrt(perpx*perpx + perpy*perpy); - perpx /= dist; - perpy /= dist; - perpx *= width; - perpy *= width; - - verts.push(p2x - perpx , p2y - perpy); - verts.push(r, g, b, alpha); - - verts.push(p2x + perpx , p2y + perpy); - verts.push(r, g, b, alpha); - - indices.push(indexStart); - - for (i = 0; i < indexCount; i++) - { - indices.push(indexStart++); - } - - indices.push(indexStart-1); -}; - -/** - * Builds a complex polygon to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildComplexPoly = function (graphicsData, webGLData) -{ - //TODO - no need to copy this as it gets turned into a FLoat32Array anyways.. - var points = graphicsData.points.slice(); - - if (points.length < 6) - { - return; - } - - // get first and last point.. figure out the middle! - var indices = webGLData.indices; - webGLData.points = points; - webGLData.alpha = graphicsData.fillAlpha; - webGLData.color = utils.hex2rgb(graphicsData.fillColor); - - // calclate the bounds.. - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - var x,y; - - // get size.. - for (var i = 0; i < points.length; i+=2) - { - x = points[i]; - y = points[i+1]; - - minX = x < minX ? x : minX; - maxX = x > maxX ? x : maxX; - - minY = y < minY ? y : minY; - maxY = y > maxY ? y : maxY; - } - - // add a quad to the end cos there is no point making another buffer! - points.push(minX, minY, - maxX, minY, - maxX, maxY, - minX, maxY); - - // push a quad onto the end.. - - //TODO - this aint needed! - var length = points.length / 2; - for (i = 0; i < length; i++) - { - indices.push( i ); - } - -}; - -/** - * Builds a polygon to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildPoly = function (graphicsData, webGLData) -{ - var points = graphicsData.points; - - if (points.length < 6) - { - return; - } - - // get first and last point.. figure out the middle! - var verts = webGLData.points; - var indices = webGLData.indices; - - var length = points.length / 2; - - // sort color - var color = utils.hex2rgb(graphicsData.fillColor); - var alpha = graphicsData.fillAlpha; - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var triangles = utils.PolyK.Triangulate(points); - - if (!triangles) { - return false; - } - - var vertPos = verts.length / 6; - - var i = 0; - - for (i = 0; i < triangles.length; i+=3) - { - indices.push(triangles[i] + vertPos); - indices.push(triangles[i] + vertPos); - indices.push(triangles[i+1] + vertPos); - indices.push(triangles[i+2] +vertPos); - indices.push(triangles[i+2] + vertPos); - } - - for (i = 0; i < length; i++) - { - verts.push(points[i * 2], points[i * 2 + 1], - r, g, b, alpha); - } - - return true; -}; - -GraphicsRenderer.graphicsDataPool = []; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js new file mode 100644 index 0000000..2a48af4 --- /dev/null +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -0,0 +1,44 @@ +/** + * + * @class + * @private + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this object renderer works for. + */ +function ObjectRenderer(renderer) +{ + /** + * The renderer used by this object renderer. + * + * @member {WebGLRenderer} + */ + this.renderer = renderer; +} + +ObjectRenderer.prototype.constructor = ObjectRenderer; +module.exports = ObjectRenderer; + +ObjectRenderer.prototype.start = function () +{ + // set the shader.. +} + +ObjectRenderer.prototype.stop = function () +{ + // flush! +} + +ObjectRenderer.prototype.flush = function () +{ + // flush! +} + +ObjectRenderer.prototype.render = function (/* object */) +{ + // render the object +} + +ObjectRenderer.prototype.destroy = function () +{ + this.renderer = null; +} diff --git a/src/core/renderers/webgl/utils/SpriteRenderer.js b/src/core/renderers/webgl/utils/SpriteRenderer.js deleted file mode 100644 index dfb2018..0000000 --- a/src/core/renderers/webgl/utils/SpriteRenderer.js +++ /dev/null @@ -1,525 +0,0 @@ -var TextureUvs = require('../../../textures/TextureUvs'), - SpriteShader = require('../shaders/SpriteShader'); - -/** - * @author Mat Groves - * - * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ - * for creating the original pixi version! - * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer - * - * Heavily inspired by LibGDX's SpriteRenderer: - * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/SpriteRenderer.java - */ - -/** - * - * @class - * @private - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this sprite batch works for. - */ -function SpriteRenderer(renderer) -{ - /** - * - * - * @member {WebGLRenderer} - */ - this.renderer = renderer; - - /** - * - * - * @member {number} - */ - this.vertSize = 5; - - /** - * - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = 2000;//Math.pow(2, 16) / this.vertSize; - - // the total number of bytes in our batch - var numVerts = this.size * 4 * this.vertByteSize; - // the total number of indices in our batch - var numIndices = this.size * 6; - - /** - * Holds the vertices - * - * @member {ArrayBuffer} - */ - this.vertices = new ArrayBuffer(numVerts); - - /** - * View on the vertices as a Float32Array - * - * @member {Float32Array} - */ - this.positions = new Float32Array(this.vertices); - - /** - * View on the vertices as a Uint32Array - * - * @member {Uint32Array} - */ - this.colors = new Uint32Array(this.vertices); - - /** - * Holds the indices - * - * @member {Uint16Array} - */ - this.indices = new Uint16Array(numIndices); - - /** - * - * - * @member {number} - */ - this.lastIndexCount = 0; - - for (var i=0, j=0; i < numIndices; i += 6, j += 4) - { - this.indices[i + 0] = j + 0; - this.indices[i + 1] = j + 1; - this.indices[i + 2] = j + 2; - this.indices[i + 3] = j + 0; - this.indices[i + 4] = j + 2; - this.indices[i + 5] = j + 3; - } - - /** - * - * - * @member {boolean} - */ - this.drawing = false; - - /** - * - * - * @member {number} - */ - this.currentBatchSize = 0; - - /** - * - * - * @member {BaseTexture} - */ - this.currentBaseTexture = null; - - /** - * - * - * @member {boolean} - */ - this.dirty = true; - - /** - * - * - * @member {Array} - */ - this.textures = []; - - /** - * - * - * @member {Array} - */ - this.blendModes = []; - - /** - * - * - * @member {Array} - */ - this.shaders = []; - - /** - * - * - * @member {Array} - */ - this.sprites = []; - - /** - * The default shader that is used if a sprite doesn't have a more specific one. - * - * @member {Shader} - */ - this.shader = null; - - // listen for context and update necessary buffers - var self = this; - this.renderer.on('context', function () - { - self.setupContext(); - }); -} - -SpriteRenderer.prototype.constructor = SpriteRenderer; -module.exports = SpriteRenderer; - -/** - * @param gl {WebGLContext} the current WebGL drawing context - */ -SpriteRenderer.prototype.setupContext = function () -{ - var gl = this.renderer.gl; - - // setup default shader - this.shader = new SpriteShader(gl); - - // create a couple of buffers - this.vertexBuffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - - // 65535 is max index, so 65535 / 6 = 10922. - - //upload the index data - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); - - this.currentBlendMode = 99999; -}; - - -/** - * @param sprite {Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite.texture; - - //TODO set blend modes.. - // check texture.. - if (this.currentBatchSize >= this.size) - { - this.flush(); - this.currentBaseTexture = texture.baseTexture; - } - - // get the uvs for the texture - var uvs = texture._uvs; - - // if the uvs have not updated then no point rendering just yet! - if (!uvs) - { - return; - } - - // TODO trim?? - var aX = sprite.anchor.x; - var aY = sprite.anchor.y; - - var w0, w1, h0, h1; - - if (texture.trim) - { - // if the sprite is trimmed then we need to add the extra space before transforming the sprite coords.. - var trim = texture.trim; - - w1 = trim.x - aX * trim.width; - w0 = w1 + texture.crop.width; - - h1 = trim.y - aY * trim.height; - h0 = h1 + texture.crop.height; - - } - else - { - w0 = (texture.frame.width ) * (1-aX); - w1 = (texture.frame.width ) * -aX; - - h0 = texture.frame.height * (1-aY); - h1 = texture.frame.height * -aY; - } - - var index = this.currentBatchSize * this.vertByteSize; - - var resolution = texture.baseTexture.resolution; - - var worldTransform = sprite.worldTransform; - - var a = worldTransform.a / resolution; - var b = worldTransform.b / resolution; - var c = worldTransform.c / resolution; - var d = worldTransform.d / resolution; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var colors = this.colors; - var positions = this.positions; - - if (this.renderer.roundPixels) - { - // xy - positions[index] = a * w1 + c * h1 + tx | 0; - positions[index+1] = d * h1 + b * w1 + ty | 0; - - // xy - positions[index+5] = a * w0 + c * h1 + tx | 0; - positions[index+6] = d * h1 + b * w0 + ty | 0; - - // xy - positions[index+10] = a * w0 + c * h0 + tx | 0; - positions[index+11] = d * h0 + b * w0 + ty | 0; - - // xy - positions[index+15] = a * w1 + c * h0 + tx | 0; - positions[index+16] = d * h0 + b * w1 + ty | 0; - } - else - { - // xy - positions[index] = a * w1 + c * h1 + tx; - positions[index+1] = d * h1 + b * w1 + ty; - - // xy - positions[index+5] = a * w0 + c * h1 + tx; - positions[index+6] = d * h1 + b * w0 + ty; - - // xy - positions[index+10] = a * w0 + c * h0 + tx; - positions[index+11] = d * h0 + b * w0 + ty; - - // xy - positions[index+15] = a * w1 + c * h0 + tx; - positions[index+16] = d * h0 + b * w1 + ty; - } - - // uv - positions[index+2] = uvs.x0; - positions[index+3] = uvs.y0; - - // uv - positions[index+7] = uvs.x1; - positions[index+8] = uvs.y1; - - // uv - positions[index+12] = uvs.x2; - positions[index+13] = uvs.y2; - - // uv - positions[index+17] = uvs.x3; - positions[index+18] = uvs.y3; - - // color and alpha - var tint = sprite.tint; - colors[index+4] = colors[index+9] = colors[index+14] = colors[index+19] = (tint >> 16) + (tint & 0xff00) + ((tint & 0xff) << 16) + (sprite.worldAlpha * 255 << 24); - - // increment the batchsize - this.sprites[this.currentBatchSize++] = sprite; - - -}; - - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - // If the batch is length 0 then return as there is nothing to draw - if (this.currentBatchSize === 0) - { - return; - } - - var gl = this.renderer.gl; - var shader; - - if (this.dirty) - { - this.dirty = false; - // bind the main texture - gl.activeTexture(gl.TEXTURE0); - - // bind the buffers - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - - // this is the same for each shader? - var stride = this.vertByteSize; - gl.vertexAttribPointer(this.shader.attributes.aVertexPosition, 2, gl.FLOAT, false, stride, 0); - gl.vertexAttribPointer(this.shader.attributes.aTextureCoord, 2, gl.FLOAT, false, stride, 2 * 4); - - // color attributes will be interpreted as unsigned bytes and normalized - gl.vertexAttribPointer(this.shader.attributes.aColor, 4, gl.UNSIGNED_BYTE, true, stride, 4 * 4); - } - - // upload the verts to the buffer - if (this.currentBatchSize > ( this.size * 0.5 ) ) - { - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertices); - } - else - { - var view = this.positions.subarray(0, this.currentBatchSize * this.vertByteSize); - gl.bufferSubData(gl.ARRAY_BUFFER, 0, view); - } - - var nextTexture, nextBlendMode, nextShader; - var batchSize = 0; - var start = 0; - - var currentBaseTexture = null; - var currentBlendMode = this.renderer.blendModeManager.currentBlendMode; - var currentShader = null; - - var blendSwap = false; - var shaderSwap = false; - var sprite; - - for (var i = 0, j = this.currentBatchSize; i < j; i++) - { - - sprite = this.sprites[i]; - - nextTexture = sprite.texture.baseTexture; - nextBlendMode = sprite.blendMode; - nextShader = sprite.shader || this.shader; - - blendSwap = currentBlendMode !== nextBlendMode; - shaderSwap = currentShader !== nextShader; // should I use uuidS??? - - if (currentBaseTexture !== nextTexture || blendSwap || shaderSwap) - { - this.renderBatch(currentBaseTexture, batchSize, start); - - start = i; - batchSize = 0; - currentBaseTexture = nextTexture; - - if (blendSwap) - { - currentBlendMode = nextBlendMode; - this.renderer.blendModeManager.setBlendMode( currentBlendMode ); - } - - if (shaderSwap) - { - currentShader = nextShader; - - shader = currentShader.shaders ? currentShader.shaders[gl.id] : currentShader; - - if (!shader) - { - shader = new Shader(gl, null, currentShader.fragmentSrc, currentShader.uniforms); - currentShader.shaders[gl.id] = shader; - } - - // set shader function??? - this.renderer.shaderManager.setShader(shader); - - if (shader.dirty) - { - shader.syncUniforms(); - } - - // both thease only need to be set if they are changing.. - // set the projection - var projection = this.renderer.projection; - gl.uniform2f(shader.uniforms.projectionVector._location, projection.x, projection.y); - - // TODO - this is temprorary! - var offsetVector = this.renderer.offset; - gl.uniform2f(shader.uniforms.offsetVector._location, offsetVector.x, offsetVector.y); - - // set the pointers - } - } - - batchSize++; - } - - this.renderBatch(currentBaseTexture, batchSize, start); - - // then reset the batch! - this.currentBatchSize = 0; -}; - -/** - * @param texture {Texture} - * @param size {number} - * @param startIndex {number} - */ -SpriteRenderer.prototype.renderBatch = function (texture, size, startIndex) -{ - if (size === 0) - { - return; - } - - var gl = this.renderer.gl; - - if (!texture._glTextures[gl.id]) - { - this.renderer.updateTexture(texture); - } - else - { - // bind the current texture - gl.bindTexture(gl.TEXTURE_2D, texture._glTextures[gl.id]); - } - - // now draw those suckas! - gl.drawElements(gl.TRIANGLES, size * 6, gl.UNSIGNED_SHORT, startIndex * 6 * 2); - - // increment the draw count - this.renderer.drawCount++; -}; - -/** - * - */ -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.dirty = true; -}; - -/** - * - */ -SpriteRenderer.prototype.start = function () -{ - this.dirty = true; -}; - -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - this.renderer.gl.deleteBuffer(this.vertexBuffer); - this.renderer.gl.deleteBuffer(this.indexBuffer); - - this.vertices = null; - this.indices = null; - - this.vertexBuffer = null; - this.indexBuffer = null; - - this.currentBaseTexture = null; - - this.renderer = null; -}; diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 0669e79..c2294f9 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,7 +500,7 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.spriteRenderer.flush(); + renderer.objectRenderers.sprite.flush(); renderer.filterManager.pushFilter(this._filterBlock); } diff --git a/src/core/display/Sprite.js b/src/core/display/Sprite.js deleted file mode 100644 index e4e9d01..0000000 --- a/src/core/display/Sprite.js +++ /dev/null @@ -1,461 +0,0 @@ -var math = require('../math'), - Texture = require('../textures/Texture'), - DisplayObjectContainer = require('./DisplayObjectContainer'), - CanvasTinter = require('../renderers/canvas/utils/CanvasTinter'), - utils = require('../utils'), - CONST = require('../const'); - -/** - * The Sprite object is the base for all textured objects that are rendered to the screen - * - * A sprite can be created directly from an image like this: - * - * ```js - * var sprite = new Sprite.fromImage('assets/image.png'); - * ``` - * - * @class Sprite - * @extends DisplayObjectContainer - * @namespace PIXI - * @param texture {Texture} The texture for this sprite - */ -function Sprite(texture) -{ - DisplayObjectContainer.call(this); - - /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting than anchor to 0.5,0.5 means the textures origin is centered - * Setting the anchor to 1,1 would mean the textures origin points will be the bottom right corner - * - * @member {Point} - */ - this.anchor = new math.Point(); - - /** - * The texture that the sprite is using - * - * @member {Texture} - * @private - */ - this._texture = null; - - /** - * The width of the sprite (this is initially set by the texture) - * - * @member {number} - * @private - */ - this._width = 0; - - /** - * The height of the sprite (this is initially set by the texture) - * - * @member {number} - * @private - */ - this._height = 0; - - /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the sprite. Set to CONST.blendModes.NORMAL to remove any blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. - * - * @member {AbstractFilter} - */ - this.shader = null; - - this.renderable = true; - - // call texture setter - this.texture = texture || Texture.EMPTY; -} - -Sprite.prototype.destroy = function (destroyTexture, destroyBaseTexture) -{ - DisplayObjectContainer.prototype.destroy.call(this); - - this.anchor = null; - - if (destroyTexture) - { - this._texture.destroy(destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// constructor -Sprite.prototype = Object.create(DisplayObjectContainer.prototype); -Sprite.prototype.constructor = Sprite; -module.exports = Sprite; - -Object.defineProperties(Sprite.prototype, { - /** - * The width of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - width: { - get: function () - { - return this.scale.x * this.texture.frame.width; - }, - set: function (value) - { - this.scale.x = value / this.texture.frame.width; - this._width = value; - } - }, - - /** - * The height of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - height: { - get: function () - { - return this.scale.y * this.texture.frame.height; - }, - set: function (value) - { - this.scale.y = value / this.texture.frame.height; - this._height = value; - } - }, - - /** - * The height of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - texture: { - get: function () - { - return this._texture; - }, - set: function (value) - { - if (this._texture === value) - { - return; - } - - this._texture = value; - this.cachedTint = 0xFFFFFF; - - if (value) - { - // wait for the texture to load - if (value.baseTexture.hasLoaded) - { - this._onTextureUpdate(); - } - else - { - value.once('update', this._onTextureUpdate.bind(this)); - } - } - } - }, -}); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = this._width / this.texture.frame.width; - } - - if (this._height) - { - this.scale.y = this._height / this.texture.frame.height; - } -}; - -Sprite.prototype._renderWebGL = function (renderer) -{ - - // this is where content gets renderd.. - // watch this space for a little render state manager.. - renderer.setObjectRenderer(renderer.spriteRenderer); - renderer.spriteRenderer.render(this); -}; - -/** - * Returns the bounds of the Sprite as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param matrix {Matrix} the transformation matrix of the sprite - * @return {Rectangle} the framing rectangle - */ -Sprite.prototype.getBounds = function (matrix) -{ - var width = this.texture.frame.width; - var height = this.texture.frame.height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = matrix || this.worldTransform ; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var minX, - maxX, - minY, - maxY; - - if(b === 0 && c === 0) - { - // scale may be negative! - if (a < 0) - { - a *= -1; - } - - if (d < 0) - { - d *= -1; - } - - // this means there is no rotation going on right? RIGHT? - // if thats the case then we can avoid checking the bound values! yay - minX = a * w1 + tx; - maxX = a * w0 + tx; - minY = d * h1 + ty; - maxY = d * h0 + ty; - } - else - { - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {CanvasRenderer} The renderer -*/ -Sprite.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || this.texture.crop.width <= 0 || this.texture.crop.height <= 0) - { - return; - } - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - renderer.context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - // Ignore null sources - if (this.texture.valid) - { - var resolution = this.texture.baseTexture.resolution / renderer.resolution; - - renderer.context.globalAlpha = this.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for this texture - if (renderer.smoothProperty && renderer.scaleMode !== this.texture.baseTexture.scaleMode) - { - renderer.scaleMode = this.texture.baseTexture.scaleMode; - renderer.context[renderer.smoothProperty] = (renderer.scaleMode === CONST.scaleModes.LINEAR); - } - - // If the texture is trimmed we offset by the trim x/y, otherwise we use the frame dimensions - var dx = (this.texture.trim ? this.texture.trim.x : 0) - (this.anchor.x * this.texture.trim.width); - var dy = (this.texture.trim ? this.texture.trim.y : 0) - (this.anchor.y * this.texture.trim.height); - - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - this.worldTransform.a, - this.worldTransform.b, - this.worldTransform.c, - this.worldTransform.d, - (this.worldTransform.tx * renderer.resolution) | 0, - (this.worldTransform.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - this.worldTransform.a, - this.worldTransform.b, - this.worldTransform.c, - this.worldTransform.d, - this.worldTransform.tx * renderer.resolution, - this.worldTransform.ty * renderer.resolution - ); - } - - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - // TODO clean up caching - how to clean up the caches? - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - - renderer.context.drawImage( - this.tintedTexture, - 0, - 0, - this.texture.crop.width, - this.texture.crop.height, - dx / resolution, - dy / resolution, - this.texture.crop.width / resolution, - this.texture.crop.height / resolution - ); - } - else - { - renderer.context.drawImage( - this.texture.baseTexture.source, - this.texture.crop.x, - this.texture.crop.y, - this.texture.crop.width, - this.texture.crop.height, - dx / resolution, - dy / resolution, - this.texture.crop.width / resolution, - this.texture.crop.height / resolution - ); - } - } - - for (var i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -// some helper functions.. - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {String} The frame Id of the texture in the cache - * @return {Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache' + this); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {String} The image url of the texture - * @return {Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/display/SpriteBatch.js b/src/core/display/SpriteBatch.js deleted file mode 100644 index c196fef..0000000 --- a/src/core/display/SpriteBatch.js +++ /dev/null @@ -1,183 +0,0 @@ -var DisplayObjectContainer = require('./DisplayObjectContainer'), - WebGLFastSpriteBatch = require('../renderers/webgl/utils/WebGLFastSpriteBatch'); - -/** - * The SpriteBatch class is a really fast version of the DisplayObjectContainer built solely for speed, - * so use when you need a lot of sprites or particles. The tradeoff of the SpriteBatch is that advanced - * functionality will not work. SpriteBatch implements only the basic object transform (position, scale, rotation). - * Any other functionality like tinting, masking, etc will not work on sprites in this batch. - * - * It's extremely easy to use : - * - * ```js - * var container = new SpriteBatch(); - * - * for(var i = 0; i < 100; ++i) - * { - * var sprite = new PIXI.Sprite.fromImage("myImage.png"); - * container.addChild(sprite); - * } - * ``` - * - * And here you have a hundred sprites that will be renderer at the speed of light. - * - * @class - * @namespace PIXI - */ - -//TODO RENAME to PARTICLE CONTAINER? -function SpriteBatch() -{ - DisplayObjectContainer.call(this); -} - -SpriteBatch.prototype = Object.create(DisplayObjectContainer.prototype); -SpriteBatch.prototype.constructor = SpriteBatch; -module.exports = SpriteBatch; - -/** - * Updates the object transform for rendering - * - * @private - */ -SpriteBatch.prototype.updateTransform = function () -{ - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.DisplayObjectContainer.prototype.updateTransform.call( this ); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} The webgl renderer - * @private - */ -SpriteBatch.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || !this.children.length) - { - return; - } - - renderer.spriteBatch.stop(); - - renderer.shaderManager.setShader(renderer.shaderManager.fastShader); - - renderer.fastSpriteBatch.begin(this); - renderer.fastSpriteBatch.render(this); - - renderer.spriteBatch.start(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} The canvas renderer - * @private - */ -SpriteBatch.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || !this.children.length) - { - return; - } - - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - var frame = child.texture.frame; - - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) - { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) - { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx, - transform.ty - ); - - isRotated = false; - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5) | 0, - ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5) | 0, - frame.width * child.scale.x, - frame.height * child.scale.y - ); - } - else - { - if (!isRotated) - { - isRotated = true; - } - - child.displayObjectUpdateTransform(); - - var childTransform = child.worldTransform; - - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx | 0, - childTransform.ty | 0 - ); - } - else - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx, - childTransform.ty - ); - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - ((child.anchor.x) * (-frame.width) + 0.5) | 0, - ((child.anchor.y) * (-frame.height) + 0.5) | 0, - frame.width, - frame.height - ); - } - } -}; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js index 70d28b1..baab0f8 100644 --- a/src/core/primitives/Graphics.js +++ b/src/core/primitives/Graphics.js @@ -738,17 +738,17 @@ return; } - + */ - + if (this.glDirty) { this.dirty = true; this.glDirty = false; } - renderer.setObjectRenderer(renderer.graphicsRenderer); - renderer.graphicsRenderer.render(this); + renderer.setObjectRenderer(renderer.objectRenderers.graphics); + renderer.objectRenderers.graphics.render(this); }; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js new file mode 100644 index 0000000..55dc7a5 --- /dev/null +++ b/src/core/primitives/GraphicsRenderer.js @@ -0,0 +1,874 @@ +var utils = require('../../../utils'), + math = require('../../../math'), + CONST = require('../../../const'), + ObjectRenderer = require('../renderers/webgl/utils/ObjectRenderer'), + WebGLRenderer = require('../renderers/webgl/WebGLRenderer'), + WebGLGraphicsData = require('./WebGLGraphicsData'); + +/** + * Renders the graphics object. + * + * @class + * @private + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this object renderer works for. + */ +function GraphicsRenderer(renderer) +{ + ObjectRenderer.call(this, renderer); + + this.graphicsDataPool = []; +} + +GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); +GraphicsRenderer.prototype.constructor = GraphicsRenderer; +module.exports = GraphicsRenderer; + +WebGLRenderer.registerObjectRenderer('graphics', GraphicsRenderer); + +/** + * Destroys this renderer. + * + */ +GraphicsRenderer.prototype.destroy = function () { + ObjectRenderer.prototype.destroy.call(this); + + this.graphicsDataPool = null; +}; + +/** + * Renders a graphics object. + * + * @param graphics {Graphics} The graphics object to render. + */ +GraphicsRenderer.prototype.render = function(graphics) +{ + var renderer = this.renderer; + var gl = renderer.gl; + + var projection = renderer.projection, + offset = renderer.offset, + shader = renderer.shaderManager.primitiveShader, + webGLData; + + if (graphics.dirty) + { + this.updateGraphics(graphics, gl); + } + + var webGL = graphics._webGL[gl.id]; + + // This could be speeded up for sure! + + renderer.blendModeManager.setBlendMode( graphics.blendMode ); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.stencilManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.stencilManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.primitiveShader; + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniform1f(shader.uniforms.flipY._location, 1); + + gl.uniform2f(shader.uniforms.projectionVector._location, projection.x, -projection.y); + gl.uniform2f(shader.uniforms.offsetVector._location, -offset.x, -offset.y); + + gl.uniform3fv(shader.uniforms.tint._location, utils.hex2rgb(graphics.tint)); + + gl.uniform1f(shader.uniforms.alpha._location, graphics.worldAlpha); + + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.attributes.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); + gl.vertexAttribPointer(shader.attributes.aColor, 4, gl.FLOAT, false,4 * 6, 2 * 4); + + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + } + } +} + +/** + * Updates the graphics object + * + * @private + * @param graphicsData {Graphics} The graphics object to update + * @param gl {WebGLContext} the current WebGL drawing context + */ +GraphicsRenderer.prototype.updateGraphics = function(graphics) +{ + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[gl.id]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[gl.id] = {lastIndex:0, data:[], gl:gl}; + } + + // flag the graphics as not dirty as we are about to update it... + graphics.dirty = false; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty) + { + graphics.clearDirty = false; + + // lop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + graphicsData.reset(); + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + if (data.type === CONST.SHAPES.POLY) + { + // need to add the points the the graphics object.. + data.points = data.shape.points.slice(); + if (data.shape.closed) + { + // close the poly if the value is true! + if (data.points[0] !== data.points[data.points.length-2] || data.points[1] !== data.points[data.points.length-1]) + { + data.points.push(data.points[0], data.points[1]); + } + } + + // MAKE SURE WE HAVE THE CORRECT TYPE.. + if (data.fill) + { + if (data.points.length >= 6) + { + if (data.points.length < 6 * 2) + { + webGLData = this.switchMode(webGL, 0); + + var canDrawUsingSimple = this.buildPoly(data, webGLData); + // console.log(canDrawUsingSimple); + + if (!canDrawUsingSimple) + { + // console.log("<>>>") + webGLData = this.switchMode(webGL, 1); + this.buildComplexPoly(data, webGLData); + } + + } + else + { + webGLData = this.switchMode(webGL, 1); + this.buildComplexPoly(data, webGLData); + } + } + } + + if (data.lineWidth > 0) + { + webGLData = this.switchMode(webGL, 0); + this.buildLine(data, webGLData); + } + } + else + { + webGLData = this.switchMode(webGL, 0); + + if (data.type === CONST.SHAPES.RECT) + { + this.buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + this.buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + this.buildRoundedRectangle(data, webGLData); + } + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } +} + +/** + * + * + * @private + * @param webGL {WebGLContext} + * @param type {number} + */ +GraphicsRenderer.prototype.switchMode = function (webGL, type) +{ + var webGLData; + + if (!webGL.data.length) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(webGL.gl); + webGLData.mode = type; + webGL.data.push(webGLData); + } + else + { + webGLData = webGL.data[webGL.data.length-1]; + + if (webGLData.mode !== type || type === 1) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(webGL.gl); + webGLData.mode = type; + webGL.data.push(webGLData); + } + } + + webGLData.dirty = true; + + return webGLData; +}; + +/** + * Builds a rectangle to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildRectangle = function (graphicsData, webGLData) +{ + // --- // + // need to convert points to a nice regular data + // + var rectData = graphicsData.shape; + var x = rectData.x; + var y = rectData.y; + var width = rectData.width; + var height = rectData.height; + + if (graphicsData.fill) + { + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var verts = webGLData.points; + var indices = webGLData.indices; + + var vertPos = verts.length/6; + + // start + verts.push(x, y); + verts.push(r, g, b, alpha); + + verts.push(x + width, y); + verts.push(r, g, b, alpha); + + verts.push(x , y + height); + verts.push(r, g, b, alpha); + + verts.push(x + width, y + height); + verts.push(r, g, b, alpha); + + // insert 2 dead triangles.. + indices.push(vertPos, vertPos, vertPos+1, vertPos+2, vertPos+3, vertPos+3); + } + + if (graphicsData.lineWidth) + { + var tempPoints = graphicsData.points; + + graphicsData.points = [x, y, + x + width, y, + x + width, y + height, + x, y + height, + x, y]; + + + this.buildLine(graphicsData, webGLData); + + graphicsData.points = tempPoints; + } +}; + +/** + * Builds a rounded rectangle to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildRoundedRectangle = function (graphicsData, webGLData) +{ + var rrectData = graphicsData.shape; + var x = rrectData.x; + var y = rrectData.y; + var width = rrectData.width; + var height = rrectData.height; + + var radius = rrectData.radius; + + var recPoints = []; + recPoints.push(x, y + radius); + recPoints = recPoints.concat(this.quadraticBezierCurve(x, y + height - radius, x, y + height, x + radius, y + height)); + recPoints = recPoints.concat(this.quadraticBezierCurve(x + width - radius, y + height, x + width, y + height, x + width, y + height - radius)); + recPoints = recPoints.concat(this.quadraticBezierCurve(x + width, y + radius, x + width, y, x + width - radius, y)); + recPoints = recPoints.concat(this.quadraticBezierCurve(x + radius, y, x, y, x, y + radius)); + + if (graphicsData.fill) + { + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var verts = webGLData.points; + var indices = webGLData.indices; + + var vecPos = verts.length/6; + + var triangles = utils.PolyK.Triangulate(recPoints); + + // + + var i = 0; + for (i = 0; i < triangles.length; i+=3) + { + indices.push(triangles[i] + vecPos); + indices.push(triangles[i] + vecPos); + indices.push(triangles[i+1] + vecPos); + indices.push(triangles[i+2] + vecPos); + indices.push(triangles[i+2] + vecPos); + } + + for (i = 0; i < recPoints.length; i++) + { + verts.push(recPoints[i], recPoints[++i], r, g, b, alpha); + } + } + + if (graphicsData.lineWidth) + { + var tempPoints = graphicsData.points; + + graphicsData.points = recPoints; + + this.buildLine(graphicsData, webGLData); + + graphicsData.points = tempPoints; + } +}; + +/** + * Calculate the points for a quadratic bezier curve. (helper function..) + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @private + * @param fromX {number} Origin point x + * @param fromY {number} Origin point x + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {number[]} + */ +GraphicsRenderer.prototype.quadraticBezierCurve = function (fromX, fromY, cpX, cpY, toX, toY) +{ + + var xa, + ya, + xb, + yb, + x, + y, + n = 20, + points = []; + + function getPt(n1 , n2, perc) { + var diff = n2 - n1; + + return n1 + ( diff * perc ); + } + + var j = 0; + for (var i = 0; i <= n; i++ ) { + j = i / n; + + // The Green Line + xa = getPt( fromX , cpX , j ); + ya = getPt( fromY , cpY , j ); + xb = getPt( cpX , toX , j ); + yb = getPt( cpY , toY , j ); + + // The Black Dot + x = getPt( xa , xb , j ); + y = getPt( ya , yb , j ); + + points.push(x, y); + } + return points; +}; + +/** + * Builds a circle to draw + * + * @private + * @param graphicsData {Graphics} The graphics object to draw + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildCircle = function (graphicsData, webGLData) +{ + // need to convert points to a nice regular data + var circleData = graphicsData.shape; + var x = circleData.x; + var y = circleData.y; + var width; + var height; + + // TODO - bit hacky?? + if (graphicsData.type === CONST.SHAPES.CIRC) + { + width = circleData.radius; + height = circleData.radius; + } + else + { + width = circleData.width; + height = circleData.height; + } + + var totalSegs = 40; + var seg = (Math.PI * 2) / totalSegs ; + + var i = 0; + + if (graphicsData.fill) + { + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var verts = webGLData.points; + var indices = webGLData.indices; + + var vecPos = verts.length/6; + + indices.push(vecPos); + + for (i = 0; i < totalSegs + 1 ; i++) + { + verts.push(x,y, r, g, b, alpha); + + verts.push(x + Math.sin(seg * i) * width, + y + Math.cos(seg * i) * height, + r, g, b, alpha); + + indices.push(vecPos++, vecPos++); + } + + indices.push(vecPos-1); + } + + if (graphicsData.lineWidth) + { + var tempPoints = graphicsData.points; + + graphicsData.points = []; + + for (i = 0; i < totalSegs + 1; i++) + { + graphicsData.points.push(x + Math.sin(seg * i) * width, + y + Math.cos(seg * i) * height); + } + + this.buildLine(graphicsData, webGLData); + + graphicsData.points = tempPoints; + } +}; + +/** + * Builds a line to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildLine = function (graphicsData, webGLData) +{ + // TODO OPTIMISE! + var i = 0; + var points = graphicsData.points; + + if (points.length === 0) + { + return; + } + + // if the line width is an odd number add 0.5 to align to a whole pixel + if (graphicsData.lineWidth%2) + { + for (i = 0; i < points.length; i++) + { + points[i] += 0.5; + } + } + + // get first and last point.. figure out the middle! + var firstPoint = new math.Point(points[0], points[1]); + var lastPoint = new math.Point(points[points.length - 2], points[points.length - 1]); + + // if the first point is the last point - gonna have issues :) + if (firstPoint.x === lastPoint.x && firstPoint.y === lastPoint.y) + { + // need to clone as we are going to slightly modify the shape.. + points = points.slice(); + + points.pop(); + points.pop(); + + lastPoint = new math.Point(points[points.length - 2], points[points.length - 1]); + + var midPointX = lastPoint.x + (firstPoint.x - lastPoint.x) *0.5; + var midPointY = lastPoint.y + (firstPoint.y - lastPoint.y) *0.5; + + points.unshift(midPointX, midPointY); + points.push(midPointX, midPointY); + } + + var verts = webGLData.points; + var indices = webGLData.indices; + var length = points.length / 2; + var indexCount = points.length; + var indexStart = verts.length/6; + + // DRAW the Line + var width = graphicsData.lineWidth / 2; + + // sort color + var color = utils.hex2rgb(graphicsData.lineColor); + var alpha = graphicsData.lineAlpha; + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var px, py, p1x, p1y, p2x, p2y, p3x, p3y; + var perpx, perpy, perp2x, perp2y, perp3x, perp3y; + var a1, b1, c1, a2, b2, c2; + var denom, pdist, dist; + + p1x = points[0]; + p1y = points[1]; + + p2x = points[2]; + p2y = points[3]; + + perpx = -(p1y - p2y); + perpy = p1x - p2x; + + dist = Math.sqrt(perpx*perpx + perpy*perpy); + + perpx /= dist; + perpy /= dist; + perpx *= width; + perpy *= width; + + // start + verts.push(p1x - perpx , p1y - perpy, + r, g, b, alpha); + + verts.push(p1x + perpx , p1y + perpy, + r, g, b, alpha); + + for (i = 1; i < length-1; i++) + { + p1x = points[(i-1)*2]; + p1y = points[(i-1)*2 + 1]; + + p2x = points[(i)*2]; + p2y = points[(i)*2 + 1]; + + p3x = points[(i+1)*2]; + p3y = points[(i+1)*2 + 1]; + + perpx = -(p1y - p2y); + perpy = p1x - p2x; + + dist = Math.sqrt(perpx*perpx + perpy*perpy); + perpx /= dist; + perpy /= dist; + perpx *= width; + perpy *= width; + + perp2x = -(p2y - p3y); + perp2y = p2x - p3x; + + dist = Math.sqrt(perp2x*perp2x + perp2y*perp2y); + perp2x /= dist; + perp2y /= dist; + perp2x *= width; + perp2y *= width; + + a1 = (-perpy + p1y) - (-perpy + p2y); + b1 = (-perpx + p2x) - (-perpx + p1x); + c1 = (-perpx + p1x) * (-perpy + p2y) - (-perpx + p2x) * (-perpy + p1y); + a2 = (-perp2y + p3y) - (-perp2y + p2y); + b2 = (-perp2x + p2x) - (-perp2x + p3x); + c2 = (-perp2x + p3x) * (-perp2y + p2y) - (-perp2x + p2x) * (-perp2y + p3y); + + denom = a1*b2 - a2*b1; + + if (Math.abs(denom) < 0.1 ) + { + + denom+=10.1; + verts.push(p2x - perpx , p2y - perpy, + r, g, b, alpha); + + verts.push(p2x + perpx , p2y + perpy, + r, g, b, alpha); + + continue; + } + + px = (b1*c2 - b2*c1)/denom; + py = (a2*c1 - a1*c2)/denom; + + + pdist = (px -p2x) * (px -p2x) + (py -p2y) + (py -p2y); + + + if (pdist > 140 * 140) + { + perp3x = perpx - perp2x; + perp3y = perpy - perp2y; + + dist = Math.sqrt(perp3x*perp3x + perp3y*perp3y); + perp3x /= dist; + perp3y /= dist; + perp3x *= width; + perp3y *= width; + + verts.push(p2x - perp3x, p2y -perp3y); + verts.push(r, g, b, alpha); + + verts.push(p2x + perp3x, p2y +perp3y); + verts.push(r, g, b, alpha); + + verts.push(p2x - perp3x, p2y -perp3y); + verts.push(r, g, b, alpha); + + indexCount++; + } + else + { + + verts.push(px , py); + verts.push(r, g, b, alpha); + + verts.push(p2x - (px-p2x), p2y - (py - p2y)); + verts.push(r, g, b, alpha); + } + } + + p1x = points[(length-2)*2]; + p1y = points[(length-2)*2 + 1]; + + p2x = points[(length-1)*2]; + p2y = points[(length-1)*2 + 1]; + + perpx = -(p1y - p2y); + perpy = p1x - p2x; + + dist = Math.sqrt(perpx*perpx + perpy*perpy); + perpx /= dist; + perpy /= dist; + perpx *= width; + perpy *= width; + + verts.push(p2x - perpx , p2y - perpy); + verts.push(r, g, b, alpha); + + verts.push(p2x + perpx , p2y + perpy); + verts.push(r, g, b, alpha); + + indices.push(indexStart); + + for (i = 0; i < indexCount; i++) + { + indices.push(indexStart++); + } + + indices.push(indexStart-1); +}; + +/** + * Builds a complex polygon to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildComplexPoly = function (graphicsData, webGLData) +{ + //TODO - no need to copy this as it gets turned into a FLoat32Array anyways.. + var points = graphicsData.points.slice(); + + if (points.length < 6) + { + return; + } + + // get first and last point.. figure out the middle! + var indices = webGLData.indices; + webGLData.points = points; + webGLData.alpha = graphicsData.fillAlpha; + webGLData.color = utils.hex2rgb(graphicsData.fillColor); + + // calclate the bounds.. + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + var x,y; + + // get size.. + for (var i = 0; i < points.length; i+=2) + { + x = points[i]; + y = points[i+1]; + + minX = x < minX ? x : minX; + maxX = x > maxX ? x : maxX; + + minY = y < minY ? y : minY; + maxY = y > maxY ? y : maxY; + } + + // add a quad to the end cos there is no point making another buffer! + points.push(minX, minY, + maxX, minY, + maxX, maxY, + minX, maxY); + + // push a quad onto the end.. + + //TODO - this aint needed! + var length = points.length / 2; + for (i = 0; i < length; i++) + { + indices.push( i ); + } + +}; + +/** + * Builds a polygon to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildPoly = function (graphicsData, webGLData) +{ + var points = graphicsData.points; + + if (points.length < 6) + { + return; + } + + // get first and last point.. figure out the middle! + var verts = webGLData.points; + var indices = webGLData.indices; + + var length = points.length / 2; + + // sort color + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var triangles = utils.PolyK.Triangulate(points); + + if (!triangles) { + return false; + } + + var vertPos = verts.length / 6; + + var i = 0; + + for (i = 0; i < triangles.length; i+=3) + { + indices.push(triangles[i] + vertPos); + indices.push(triangles[i] + vertPos); + indices.push(triangles[i+1] + vertPos); + indices.push(triangles[i+2] +vertPos); + indices.push(triangles[i+2] + vertPos); + } + + for (i = 0; i < length; i++) + { + verts.push(points[i * 2], points[i * 2 + 1], + r, g, b, alpha); + } + + return true; +}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 547bdc7..b0189c5 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,6 +1,4 @@ -var SpriteRenderer = require('./utils/SpriteRenderer'), - GraphicsRenderer = require('./utils/GraphicsRenderer'), - WebGLFastSpriteBatch = require('./utils/WebGLFastSpriteBatch'), +var WebGLFastSpriteBatch = require('./utils/WebGLFastSpriteBatch'), WebGLShaderManager = require('./managers/WebGLShaderManager'), WebGLMaskManager = require('./managers/WebGLMaskManager'), WebGLFilterManager = require('./managers/WebGLFilterManager'), @@ -28,21 +26,21 @@ * @param [options.preserveDrawingBuffer=false] {boolean} enables drawing buffer preservation, enable this if you need to call toDataUrl on the webgl context * @param [options.resolution=1] {number} the resolution of the renderer retina would be 2 */ -function WebGLRenderer(width, height, options) +function WebGLRenderer(width, height, options) { utils.sayHello('webGL'); - if (options) + if (options) { - for (var i in CONST.defaultRenderOptions) + for (var i in CONST.defaultRenderOptions) { - if (typeof options[i] === 'undefined') + if (typeof options[i] === 'undefined') { options[i] = CONST.defaultRenderOptions[i]; } } } - else + else { options = CONST.defaultRenderOptions; } @@ -191,14 +189,6 @@ /** * Manages the rendering of sprites - * @member {SpriteRenderer} - */ - this.spriteRenderer = new SpriteRenderer(this); - - this.graphicsRenderer = new GraphicsRenderer(this); - - /** - * Manages the rendering of sprites * @member {WebGLFastSpriteBatch} */ this.fastSpriteBatch = new WebGLFastSpriteBatch(this); @@ -245,8 +235,17 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - this.currentRenderer = this.spriteRenderer; + /** + * Manages the renderer of specific objects. + * + * @member {object} + */ + this.objectRenderers = {}; + // create an instance of each registered object renderer + for (var o in WebGLRenderer._objectRenderers) { + this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); + } } // constructor @@ -255,21 +254,20 @@ utils.eventTarget.mixin(WebGLRenderer.prototype); -Object.defineProperties(WebGLRenderer.prototype, -{ +Object.defineProperties(WebGLRenderer.prototype, { /** * The background color to fill if not transparent * * @member {number} * @memberof WebGLRenderer# */ - backgroundColor: + backgroundColor: { - get: function () + get: function () { return this._backgroundColor; }, - set: function (val) + set: function (val) { this._backgroundColor = val; utils.hex2rgb(val, this._backgroundColorRgb); @@ -281,12 +279,12 @@ * * @private */ -WebGLRenderer.prototype._initContext = function () +WebGLRenderer.prototype._initContext = function () { var gl = this.view.getContext('webgl', this._contextOptions) || this.view.getContext('experimental-webgl', this._contextOptions); this.gl = gl; - if (!gl) + if (!gl) { // fail, not able to get a context throw new Error('This browser does not support webGL. Try using the canvas renderer'); @@ -312,10 +310,10 @@ * * @param object {DisplayObject} the object to be rendered */ -WebGLRenderer.prototype.render = function (object) +WebGLRenderer.prototype.render = function (object) { // no point rendering if our context has been blown up! - if (this.gl.isContextLost()) + if (this.gl.isContextLost()) { return; } @@ -336,13 +334,13 @@ // make sure we are bound to the main frame buffer gl.bindFramebuffer(gl.FRAMEBUFFER, null); - if (this.clearBeforeRender) + if (this.clearBeforeRender) { - if (this.transparent) + if (this.transparent) { gl.clearColor(0, 0, 0, 0); } - else + else { gl.clearColor(this._backgroundColorRgb[0], this._backgroundColorRgb[1], this._backgroundColorRgb[2], 1); } @@ -360,7 +358,7 @@ * @param projection {Point} The projection * @param buffer {Array} a standard WebGL buffer */ -WebGLRenderer.prototype.renderDisplayObject = function (displayObject, projection, buffer) +WebGLRenderer.prototype.renderDisplayObject = function (displayObject, projection, buffer) { this.blendModeManager.setBlendMode(CONST.blendModes.NORMAL); @@ -386,9 +384,9 @@ this.currentRenderer.flush(); }; -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) +WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) { - if(this.currentRenderer === objectRenderer) + if (this.currentRenderer === objectRenderer) { return; } @@ -396,7 +394,7 @@ this.currentRenderer.stop(); this.currentRenderer = objectRenderer; this.currentRenderer.start(); -} +}; /** * Resizes the webGL view to the specified width and height. @@ -404,7 +402,7 @@ * @param width {number} the new width of the webGL view * @param height {number} the new height of the webGL view */ -WebGLRenderer.prototype.resize = function (width, height) +WebGLRenderer.prototype.resize = function (width, height) { this.width = width * this.resolution; this.height = height * this.resolution; @@ -412,7 +410,7 @@ this.view.width = this.width; this.view.height = this.height; - if (this.autoResize) + if (this.autoResize) { this.view.style.width = this.width / this.resolution + 'px'; this.view.style.height = this.height / this.resolution + 'px'; @@ -429,18 +427,18 @@ * * @param texture {BaseTexture|Texture} the texture to update */ -WebGLRenderer.prototype.updateTexture = function (texture) +WebGLRenderer.prototype.updateTexture = function (texture) { texture = texture.baseTexture || texture; - if (!texture.hasLoaded) + if (!texture.hasLoaded) { return; } var gl = this.gl; - if (!texture._glTextures[gl.id]) + if (!texture._glTextures[gl.id]) { texture._glTextures[gl.id] = gl.createTexture(); texture.on('update', this._boundUpdateTexture); @@ -455,22 +453,22 @@ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, texture.scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); - if (texture.mipmap && utils.isPowerOfTwo(texture.width, texture.height)) + if (texture.mipmap && utils.isPowerOfTwo(texture.width, texture.height)) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); gl.generateMipmap(gl.TEXTURE_2D); } - else + else { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); } - if (!texture._powerOf2) + if (!texture._powerOf2) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); } - else + else { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); @@ -479,16 +477,16 @@ return texture._glTextures[gl.id]; }; -WebGLRenderer.prototype.destroyTexture = function (texture) +WebGLRenderer.prototype.destroyTexture = function (texture) { texture = texture.baseTexture || texture; - if (!texture.hasLoaded) + if (!texture.hasLoaded) { return; } - if (texture._glTextures[this.gl.id]) + if (texture._glTextures[this.gl.id]) { this.gl.deleteTexture(texture._glTextures[this.gl.id]); } @@ -500,7 +498,7 @@ * @param event {Event} * @private */ -WebGLRenderer.prototype.handleContextLost = function (event) +WebGLRenderer.prototype.handleContextLost = function (event) { event.preventDefault(); }; @@ -511,12 +509,12 @@ * @param event {Event} * @private */ -WebGLRenderer.prototype.handleContextRestored = function () +WebGLRenderer.prototype.handleContextRestored = function () { this._initContext(); // empty all the ol gl textures as they are useless now - for (var key in utils.TextureCache) + for (var key in utils.TextureCache) { var texture = utils.TextureCache[key].baseTexture; texture._glTextures = []; @@ -528,9 +526,9 @@ * * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ -WebGLRenderer.prototype.destroy = function (removeView) +WebGLRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parent) + if (removeView && this.view.parent) { this.view.parent.removeChild(this.view); } @@ -541,10 +539,15 @@ // time to create the render managers! each one focuses on managine a state in webGL this.shaderManager.destroy(); - this.spriteRenderer.destroy(); this.maskManager.destroy(); this.filterManager.destroy(); + for (var o in this.objectRenderers) { + this.objectRenderers[o].destroy(); + this.objectRenderers[o] = null; + } + + this.objectRenderers = null; // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -574,7 +577,6 @@ this.drawCount = 0; this.shaderManager = null; - this.spriteRenderer = null; this.maskManager = null; this.filterManager = null; this.stencilManager = null; @@ -591,11 +593,11 @@ * * @private */ -WebGLRenderer.prototype._mapBlendModes = function () +WebGLRenderer.prototype._mapBlendModes = function () { var gl = this.gl; - if (!this.blendModes) + if (!this.blendModes) { this.blendModes = {}; @@ -620,3 +622,9 @@ }; WebGLRenderer.glContextId = 0; +WebGLRenderer._objectRenderers = {}; + +WebGLRenderer.registerObjectRenderer = function (name, ctor) { + WebGLRenderer._objectRenderers[name] = ctor; +}; + diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js index 773cc5d..e81ca56 100644 --- a/src/core/renderers/webgl/managers/WebGLMaskManager.js +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -20,11 +20,11 @@ */ WebGLMaskManager.prototype.pushMask = function (maskData) { - this.renderer.setObjectRenderer( this.renderer.graphicsRenderer ); + this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); if (maskData.dirty) { - this.renderer.graphicsRenderer.updateGraphics(maskData, this.renderer.gl); + this.renderer.objectRenderers.graphics.updateGraphics(maskData, this.renderer.gl); } if (!maskData._webGL[this.renderer.gl.id].data.length) @@ -42,7 +42,7 @@ */ WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.setObjectRenderer( this.renderer.graphicsRenderer ); - + this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.stencilManager.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); }; diff --git a/src/core/renderers/webgl/utils/GraphicsRenderer.js b/src/core/renderers/webgl/utils/GraphicsRenderer.js deleted file mode 100644 index 6713275..0000000 --- a/src/core/renderers/webgl/utils/GraphicsRenderer.js +++ /dev/null @@ -1,894 +0,0 @@ -var utils = require('../../../utils'), - math = require('../../../math'), - CONST = require('../../../const'), - WebGLGraphicsData = require('./WebGLGraphicsData'); - -/** - * A set of functions used by the webGL renderer to draw the primitive graphics data - * - * @namespace PIXI - * @private - */ -var WebGLGraphics = module.exports = {}; - -/** - * Renders the graphics object - * - * @static - * @private - * @param graphics {Graphics} - * @param renderer {WebGLRenderer} - */ - -var GraphicsRenderer = function(renderer) -{ - this.renderer = renderer; - this.graphicsDataPool = []; -} - -module.exports = GraphicsRenderer; - -GraphicsRenderer.prototype.start = function() -{ - // set the shader.. - -} - -GraphicsRenderer.prototype.stop = function() -{ - // flush! -} - -GraphicsRenderer.prototype.flush = function() -{ - // flush! -} - - - -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var projection = renderer.projection, - offset = renderer.offset, - shader = renderer.shaderManager.primitiveShader, - webGLData; - - if (graphics.dirty) - { - this.updateGraphics(graphics, gl); - } - - var webGL = graphics._webGL[gl.id]; - - // This could be speeded up for sure! - - renderer.blendModeManager.setBlendMode( graphics.blendMode ); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.stencilManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.stencilManager.popStencil(graphics, webGLData, renderer); - } - else - { - webGLData = webGL.data[i]; - - - renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); - shader = renderer.shaderManager.primitiveShader; - gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); - - gl.uniform1f(shader.uniforms.flipY._location, 1); - - gl.uniform2f(shader.uniforms.projectionVector._location, projection.x, -projection.y); - gl.uniform2f(shader.uniforms.offsetVector._location, -offset.x, -offset.y); - - gl.uniform3fv(shader.uniforms.tint._location, utils.hex2rgb(graphics.tint)); - - gl.uniform1f(shader.uniforms.alpha._location, graphics.worldAlpha); - - - gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); - - gl.vertexAttribPointer(shader.attributes.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); - gl.vertexAttribPointer(shader.attributes.aColor, 4, gl.FLOAT, false,4 * 6, 2 * 4); - - // set the index buffer! - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); - gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); - } - } -} - -/** - * Updates the graphics object - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object to update - * @param gl {WebGLContext} the current WebGL drawing context - */ - -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[gl.id]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[gl.id] = {lastIndex:0, data:[], gl:gl}; - } - - // flag the graphics as not dirty as we are about to update it... - graphics.dirty = false; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty) - { - graphics.clearDirty = false; - - // lop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - graphicsData.reset(); - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - if (data.type === CONST.SHAPES.POLY) - { - // need to add the points the the graphics object.. - data.points = data.shape.points.slice(); - if (data.shape.closed) - { - // close the poly if the value is true! - if (data.points[0] !== data.points[data.points.length-2] || data.points[1] !== data.points[data.points.length-1]) - { - data.points.push(data.points[0], data.points[1]); - } - } - - // MAKE SURE WE HAVE THE CORRECT TYPE.. - if (data.fill) - { - if (data.points.length >= 6) - { - if (data.points.length < 6 * 2) - { - webGLData = this.switchMode(webGL, 0); - - var canDrawUsingSimple = this.buildPoly(data, webGLData); - // console.log(canDrawUsingSimple); - - if (!canDrawUsingSimple) - { - // console.log("<>>>") - webGLData = this.switchMode(webGL, 1); - this.buildComplexPoly(data, webGLData); - } - - } - else - { - webGLData = this.switchMode(webGL, 1); - this.buildComplexPoly(data, webGLData); - } - } - } - - if (data.lineWidth > 0) - { - webGLData = this.switchMode(webGL, 0); - this.buildLine(data, webGLData); - } - } - else - { - webGLData = this.switchMode(webGL, 0); - - if (data.type === CONST.SHAPES.RECT) - { - this.buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - this.buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - this.buildRoundedRectangle(data, webGLData); - } - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -} - -/** - * @static - * @private - * @param webGL {WebGLContext} - * @param type {number} - */ -GraphicsRenderer.prototype.switchMode = function (webGL, type) -{ - var webGLData; - - if (!webGL.data.length) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(webGL.gl); - webGLData.mode = type; - webGL.data.push(webGLData); - } - else - { - webGLData = webGL.data[webGL.data.length-1]; - - if (webGLData.mode !== type || type === 1) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(webGL.gl); - webGLData.mode = type; - webGL.data.push(webGLData); - } - } - - webGLData.dirty = true; - - return webGLData; -}; - - - - - - -/** - * Builds a rectangle to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildRectangle = function (graphicsData, webGLData) -{ - // --- // - // need to convert points to a nice regular data - // - var rectData = graphicsData.shape; - var x = rectData.x; - var y = rectData.y; - var width = rectData.width; - var height = rectData.height; - - if (graphicsData.fill) - { - var color = utils.hex2rgb(graphicsData.fillColor); - var alpha = graphicsData.fillAlpha; - - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var verts = webGLData.points; - var indices = webGLData.indices; - - var vertPos = verts.length/6; - - // start - verts.push(x, y); - verts.push(r, g, b, alpha); - - verts.push(x + width, y); - verts.push(r, g, b, alpha); - - verts.push(x , y + height); - verts.push(r, g, b, alpha); - - verts.push(x + width, y + height); - verts.push(r, g, b, alpha); - - // insert 2 dead triangles.. - indices.push(vertPos, vertPos, vertPos+1, vertPos+2, vertPos+3, vertPos+3); - } - - if (graphicsData.lineWidth) - { - var tempPoints = graphicsData.points; - - graphicsData.points = [x, y, - x + width, y, - x + width, y + height, - x, y + height, - x, y]; - - - this.buildLine(graphicsData, webGLData); - - graphicsData.points = tempPoints; - } -}; - -/** - * Builds a rounded rectangle to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildRoundedRectangle = function (graphicsData, webGLData) -{ - var rrectData = graphicsData.shape; - var x = rrectData.x; - var y = rrectData.y; - var width = rrectData.width; - var height = rrectData.height; - - var radius = rrectData.radius; - - var recPoints = []; - recPoints.push(x, y + radius); - recPoints = recPoints.concat(this.quadraticBezierCurve(x, y + height - radius, x, y + height, x + radius, y + height)); - recPoints = recPoints.concat(this.quadraticBezierCurve(x + width - radius, y + height, x + width, y + height, x + width, y + height - radius)); - recPoints = recPoints.concat(this.quadraticBezierCurve(x + width, y + radius, x + width, y, x + width - radius, y)); - recPoints = recPoints.concat(this.quadraticBezierCurve(x + radius, y, x, y, x, y + radius)); - - if (graphicsData.fill) - { - var color = utils.hex2rgb(graphicsData.fillColor); - var alpha = graphicsData.fillAlpha; - - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var verts = webGLData.points; - var indices = webGLData.indices; - - var vecPos = verts.length/6; - - var triangles = utils.PolyK.Triangulate(recPoints); - - // - - var i = 0; - for (i = 0; i < triangles.length; i+=3) - { - indices.push(triangles[i] + vecPos); - indices.push(triangles[i] + vecPos); - indices.push(triangles[i+1] + vecPos); - indices.push(triangles[i+2] + vecPos); - indices.push(triangles[i+2] + vecPos); - } - - for (i = 0; i < recPoints.length; i++) - { - verts.push(recPoints[i], recPoints[++i], r, g, b, alpha); - } - } - - if (graphicsData.lineWidth) - { - var tempPoints = graphicsData.points; - - graphicsData.points = recPoints; - - this.buildLine(graphicsData, webGLData); - - graphicsData.points = tempPoints; - } -}; - -/** - * Calculate the points for a quadratic bezier curve. (helper function..) - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @static - * @private - * @param fromX {number} Origin point x - * @param fromY {number} Origin point x - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {number[]} - */ -GraphicsRenderer.prototype.quadraticBezierCurve = function (fromX, fromY, cpX, cpY, toX, toY) -{ - - var xa, - ya, - xb, - yb, - x, - y, - n = 20, - points = []; - - function getPt(n1 , n2, perc) { - var diff = n2 - n1; - - return n1 + ( diff * perc ); - } - - var j = 0; - for (var i = 0; i <= n; i++ ) { - j = i / n; - - // The Green Line - xa = getPt( fromX , cpX , j ); - ya = getPt( fromY , cpY , j ); - xb = getPt( cpX , toX , j ); - yb = getPt( cpY , toY , j ); - - // The Black Dot - x = getPt( xa , xb , j ); - y = getPt( ya , yb , j ); - - points.push(x, y); - } - return points; -}; - -/** - * Builds a circle to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object to draw - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildCircle = function (graphicsData, webGLData) -{ - // need to convert points to a nice regular data - var circleData = graphicsData.shape; - var x = circleData.x; - var y = circleData.y; - var width; - var height; - - // TODO - bit hacky?? - if (graphicsData.type === CONST.SHAPES.CIRC) - { - width = circleData.radius; - height = circleData.radius; - } - else - { - width = circleData.width; - height = circleData.height; - } - - var totalSegs = 40; - var seg = (Math.PI * 2) / totalSegs ; - - var i = 0; - - if (graphicsData.fill) - { - var color = utils.hex2rgb(graphicsData.fillColor); - var alpha = graphicsData.fillAlpha; - - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var verts = webGLData.points; - var indices = webGLData.indices; - - var vecPos = verts.length/6; - - indices.push(vecPos); - - for (i = 0; i < totalSegs + 1 ; i++) - { - verts.push(x,y, r, g, b, alpha); - - verts.push(x + Math.sin(seg * i) * width, - y + Math.cos(seg * i) * height, - r, g, b, alpha); - - indices.push(vecPos++, vecPos++); - } - - indices.push(vecPos-1); - } - - if (graphicsData.lineWidth) - { - var tempPoints = graphicsData.points; - - graphicsData.points = []; - - for (i = 0; i < totalSegs + 1; i++) - { - graphicsData.points.push(x + Math.sin(seg * i) * width, - y + Math.cos(seg * i) * height); - } - - this.buildLine(graphicsData, webGLData); - - graphicsData.points = tempPoints; - } -}; - -/** - * Builds a line to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildLine = function (graphicsData, webGLData) -{ - // TODO OPTIMISE! - var i = 0; - var points = graphicsData.points; - - if (points.length === 0) - { - return; - } - - // if the line width is an odd number add 0.5 to align to a whole pixel - if (graphicsData.lineWidth%2) - { - for (i = 0; i < points.length; i++) - { - points[i] += 0.5; - } - } - - // get first and last point.. figure out the middle! - var firstPoint = new math.Point(points[0], points[1]); - var lastPoint = new math.Point(points[points.length - 2], points[points.length - 1]); - - // if the first point is the last point - gonna have issues :) - if (firstPoint.x === lastPoint.x && firstPoint.y === lastPoint.y) - { - // need to clone as we are going to slightly modify the shape.. - points = points.slice(); - - points.pop(); - points.pop(); - - lastPoint = new math.Point(points[points.length - 2], points[points.length - 1]); - - var midPointX = lastPoint.x + (firstPoint.x - lastPoint.x) *0.5; - var midPointY = lastPoint.y + (firstPoint.y - lastPoint.y) *0.5; - - points.unshift(midPointX, midPointY); - points.push(midPointX, midPointY); - } - - var verts = webGLData.points; - var indices = webGLData.indices; - var length = points.length / 2; - var indexCount = points.length; - var indexStart = verts.length/6; - - // DRAW the Line - var width = graphicsData.lineWidth / 2; - - // sort color - var color = utils.hex2rgb(graphicsData.lineColor); - var alpha = graphicsData.lineAlpha; - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var px, py, p1x, p1y, p2x, p2y, p3x, p3y; - var perpx, perpy, perp2x, perp2y, perp3x, perp3y; - var a1, b1, c1, a2, b2, c2; - var denom, pdist, dist; - - p1x = points[0]; - p1y = points[1]; - - p2x = points[2]; - p2y = points[3]; - - perpx = -(p1y - p2y); - perpy = p1x - p2x; - - dist = Math.sqrt(perpx*perpx + perpy*perpy); - - perpx /= dist; - perpy /= dist; - perpx *= width; - perpy *= width; - - // start - verts.push(p1x - perpx , p1y - perpy, - r, g, b, alpha); - - verts.push(p1x + perpx , p1y + perpy, - r, g, b, alpha); - - for (i = 1; i < length-1; i++) - { - p1x = points[(i-1)*2]; - p1y = points[(i-1)*2 + 1]; - - p2x = points[(i)*2]; - p2y = points[(i)*2 + 1]; - - p3x = points[(i+1)*2]; - p3y = points[(i+1)*2 + 1]; - - perpx = -(p1y - p2y); - perpy = p1x - p2x; - - dist = Math.sqrt(perpx*perpx + perpy*perpy); - perpx /= dist; - perpy /= dist; - perpx *= width; - perpy *= width; - - perp2x = -(p2y - p3y); - perp2y = p2x - p3x; - - dist = Math.sqrt(perp2x*perp2x + perp2y*perp2y); - perp2x /= dist; - perp2y /= dist; - perp2x *= width; - perp2y *= width; - - a1 = (-perpy + p1y) - (-perpy + p2y); - b1 = (-perpx + p2x) - (-perpx + p1x); - c1 = (-perpx + p1x) * (-perpy + p2y) - (-perpx + p2x) * (-perpy + p1y); - a2 = (-perp2y + p3y) - (-perp2y + p2y); - b2 = (-perp2x + p2x) - (-perp2x + p3x); - c2 = (-perp2x + p3x) * (-perp2y + p2y) - (-perp2x + p2x) * (-perp2y + p3y); - - denom = a1*b2 - a2*b1; - - if (Math.abs(denom) < 0.1 ) - { - - denom+=10.1; - verts.push(p2x - perpx , p2y - perpy, - r, g, b, alpha); - - verts.push(p2x + perpx , p2y + perpy, - r, g, b, alpha); - - continue; - } - - px = (b1*c2 - b2*c1)/denom; - py = (a2*c1 - a1*c2)/denom; - - - pdist = (px -p2x) * (px -p2x) + (py -p2y) + (py -p2y); - - - if (pdist > 140 * 140) - { - perp3x = perpx - perp2x; - perp3y = perpy - perp2y; - - dist = Math.sqrt(perp3x*perp3x + perp3y*perp3y); - perp3x /= dist; - perp3y /= dist; - perp3x *= width; - perp3y *= width; - - verts.push(p2x - perp3x, p2y -perp3y); - verts.push(r, g, b, alpha); - - verts.push(p2x + perp3x, p2y +perp3y); - verts.push(r, g, b, alpha); - - verts.push(p2x - perp3x, p2y -perp3y); - verts.push(r, g, b, alpha); - - indexCount++; - } - else - { - - verts.push(px , py); - verts.push(r, g, b, alpha); - - verts.push(p2x - (px-p2x), p2y - (py - p2y)); - verts.push(r, g, b, alpha); - } - } - - p1x = points[(length-2)*2]; - p1y = points[(length-2)*2 + 1]; - - p2x = points[(length-1)*2]; - p2y = points[(length-1)*2 + 1]; - - perpx = -(p1y - p2y); - perpy = p1x - p2x; - - dist = Math.sqrt(perpx*perpx + perpy*perpy); - perpx /= dist; - perpy /= dist; - perpx *= width; - perpy *= width; - - verts.push(p2x - perpx , p2y - perpy); - verts.push(r, g, b, alpha); - - verts.push(p2x + perpx , p2y + perpy); - verts.push(r, g, b, alpha); - - indices.push(indexStart); - - for (i = 0; i < indexCount; i++) - { - indices.push(indexStart++); - } - - indices.push(indexStart-1); -}; - -/** - * Builds a complex polygon to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildComplexPoly = function (graphicsData, webGLData) -{ - //TODO - no need to copy this as it gets turned into a FLoat32Array anyways.. - var points = graphicsData.points.slice(); - - if (points.length < 6) - { - return; - } - - // get first and last point.. figure out the middle! - var indices = webGLData.indices; - webGLData.points = points; - webGLData.alpha = graphicsData.fillAlpha; - webGLData.color = utils.hex2rgb(graphicsData.fillColor); - - // calclate the bounds.. - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - var x,y; - - // get size.. - for (var i = 0; i < points.length; i+=2) - { - x = points[i]; - y = points[i+1]; - - minX = x < minX ? x : minX; - maxX = x > maxX ? x : maxX; - - minY = y < minY ? y : minY; - maxY = y > maxY ? y : maxY; - } - - // add a quad to the end cos there is no point making another buffer! - points.push(minX, minY, - maxX, minY, - maxX, maxY, - minX, maxY); - - // push a quad onto the end.. - - //TODO - this aint needed! - var length = points.length / 2; - for (i = 0; i < length; i++) - { - indices.push( i ); - } - -}; - -/** - * Builds a polygon to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildPoly = function (graphicsData, webGLData) -{ - var points = graphicsData.points; - - if (points.length < 6) - { - return; - } - - // get first and last point.. figure out the middle! - var verts = webGLData.points; - var indices = webGLData.indices; - - var length = points.length / 2; - - // sort color - var color = utils.hex2rgb(graphicsData.fillColor); - var alpha = graphicsData.fillAlpha; - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var triangles = utils.PolyK.Triangulate(points); - - if (!triangles) { - return false; - } - - var vertPos = verts.length / 6; - - var i = 0; - - for (i = 0; i < triangles.length; i+=3) - { - indices.push(triangles[i] + vertPos); - indices.push(triangles[i] + vertPos); - indices.push(triangles[i+1] + vertPos); - indices.push(triangles[i+2] +vertPos); - indices.push(triangles[i+2] + vertPos); - } - - for (i = 0; i < length; i++) - { - verts.push(points[i * 2], points[i * 2 + 1], - r, g, b, alpha); - } - - return true; -}; - -GraphicsRenderer.graphicsDataPool = []; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js new file mode 100644 index 0000000..2a48af4 --- /dev/null +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -0,0 +1,44 @@ +/** + * + * @class + * @private + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this object renderer works for. + */ +function ObjectRenderer(renderer) +{ + /** + * The renderer used by this object renderer. + * + * @member {WebGLRenderer} + */ + this.renderer = renderer; +} + +ObjectRenderer.prototype.constructor = ObjectRenderer; +module.exports = ObjectRenderer; + +ObjectRenderer.prototype.start = function () +{ + // set the shader.. +} + +ObjectRenderer.prototype.stop = function () +{ + // flush! +} + +ObjectRenderer.prototype.flush = function () +{ + // flush! +} + +ObjectRenderer.prototype.render = function (/* object */) +{ + // render the object +} + +ObjectRenderer.prototype.destroy = function () +{ + this.renderer = null; +} diff --git a/src/core/renderers/webgl/utils/SpriteRenderer.js b/src/core/renderers/webgl/utils/SpriteRenderer.js deleted file mode 100644 index dfb2018..0000000 --- a/src/core/renderers/webgl/utils/SpriteRenderer.js +++ /dev/null @@ -1,525 +0,0 @@ -var TextureUvs = require('../../../textures/TextureUvs'), - SpriteShader = require('../shaders/SpriteShader'); - -/** - * @author Mat Groves - * - * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ - * for creating the original pixi version! - * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer - * - * Heavily inspired by LibGDX's SpriteRenderer: - * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/SpriteRenderer.java - */ - -/** - * - * @class - * @private - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this sprite batch works for. - */ -function SpriteRenderer(renderer) -{ - /** - * - * - * @member {WebGLRenderer} - */ - this.renderer = renderer; - - /** - * - * - * @member {number} - */ - this.vertSize = 5; - - /** - * - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = 2000;//Math.pow(2, 16) / this.vertSize; - - // the total number of bytes in our batch - var numVerts = this.size * 4 * this.vertByteSize; - // the total number of indices in our batch - var numIndices = this.size * 6; - - /** - * Holds the vertices - * - * @member {ArrayBuffer} - */ - this.vertices = new ArrayBuffer(numVerts); - - /** - * View on the vertices as a Float32Array - * - * @member {Float32Array} - */ - this.positions = new Float32Array(this.vertices); - - /** - * View on the vertices as a Uint32Array - * - * @member {Uint32Array} - */ - this.colors = new Uint32Array(this.vertices); - - /** - * Holds the indices - * - * @member {Uint16Array} - */ - this.indices = new Uint16Array(numIndices); - - /** - * - * - * @member {number} - */ - this.lastIndexCount = 0; - - for (var i=0, j=0; i < numIndices; i += 6, j += 4) - { - this.indices[i + 0] = j + 0; - this.indices[i + 1] = j + 1; - this.indices[i + 2] = j + 2; - this.indices[i + 3] = j + 0; - this.indices[i + 4] = j + 2; - this.indices[i + 5] = j + 3; - } - - /** - * - * - * @member {boolean} - */ - this.drawing = false; - - /** - * - * - * @member {number} - */ - this.currentBatchSize = 0; - - /** - * - * - * @member {BaseTexture} - */ - this.currentBaseTexture = null; - - /** - * - * - * @member {boolean} - */ - this.dirty = true; - - /** - * - * - * @member {Array} - */ - this.textures = []; - - /** - * - * - * @member {Array} - */ - this.blendModes = []; - - /** - * - * - * @member {Array} - */ - this.shaders = []; - - /** - * - * - * @member {Array} - */ - this.sprites = []; - - /** - * The default shader that is used if a sprite doesn't have a more specific one. - * - * @member {Shader} - */ - this.shader = null; - - // listen for context and update necessary buffers - var self = this; - this.renderer.on('context', function () - { - self.setupContext(); - }); -} - -SpriteRenderer.prototype.constructor = SpriteRenderer; -module.exports = SpriteRenderer; - -/** - * @param gl {WebGLContext} the current WebGL drawing context - */ -SpriteRenderer.prototype.setupContext = function () -{ - var gl = this.renderer.gl; - - // setup default shader - this.shader = new SpriteShader(gl); - - // create a couple of buffers - this.vertexBuffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - - // 65535 is max index, so 65535 / 6 = 10922. - - //upload the index data - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); - - this.currentBlendMode = 99999; -}; - - -/** - * @param sprite {Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite.texture; - - //TODO set blend modes.. - // check texture.. - if (this.currentBatchSize >= this.size) - { - this.flush(); - this.currentBaseTexture = texture.baseTexture; - } - - // get the uvs for the texture - var uvs = texture._uvs; - - // if the uvs have not updated then no point rendering just yet! - if (!uvs) - { - return; - } - - // TODO trim?? - var aX = sprite.anchor.x; - var aY = sprite.anchor.y; - - var w0, w1, h0, h1; - - if (texture.trim) - { - // if the sprite is trimmed then we need to add the extra space before transforming the sprite coords.. - var trim = texture.trim; - - w1 = trim.x - aX * trim.width; - w0 = w1 + texture.crop.width; - - h1 = trim.y - aY * trim.height; - h0 = h1 + texture.crop.height; - - } - else - { - w0 = (texture.frame.width ) * (1-aX); - w1 = (texture.frame.width ) * -aX; - - h0 = texture.frame.height * (1-aY); - h1 = texture.frame.height * -aY; - } - - var index = this.currentBatchSize * this.vertByteSize; - - var resolution = texture.baseTexture.resolution; - - var worldTransform = sprite.worldTransform; - - var a = worldTransform.a / resolution; - var b = worldTransform.b / resolution; - var c = worldTransform.c / resolution; - var d = worldTransform.d / resolution; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var colors = this.colors; - var positions = this.positions; - - if (this.renderer.roundPixels) - { - // xy - positions[index] = a * w1 + c * h1 + tx | 0; - positions[index+1] = d * h1 + b * w1 + ty | 0; - - // xy - positions[index+5] = a * w0 + c * h1 + tx | 0; - positions[index+6] = d * h1 + b * w0 + ty | 0; - - // xy - positions[index+10] = a * w0 + c * h0 + tx | 0; - positions[index+11] = d * h0 + b * w0 + ty | 0; - - // xy - positions[index+15] = a * w1 + c * h0 + tx | 0; - positions[index+16] = d * h0 + b * w1 + ty | 0; - } - else - { - // xy - positions[index] = a * w1 + c * h1 + tx; - positions[index+1] = d * h1 + b * w1 + ty; - - // xy - positions[index+5] = a * w0 + c * h1 + tx; - positions[index+6] = d * h1 + b * w0 + ty; - - // xy - positions[index+10] = a * w0 + c * h0 + tx; - positions[index+11] = d * h0 + b * w0 + ty; - - // xy - positions[index+15] = a * w1 + c * h0 + tx; - positions[index+16] = d * h0 + b * w1 + ty; - } - - // uv - positions[index+2] = uvs.x0; - positions[index+3] = uvs.y0; - - // uv - positions[index+7] = uvs.x1; - positions[index+8] = uvs.y1; - - // uv - positions[index+12] = uvs.x2; - positions[index+13] = uvs.y2; - - // uv - positions[index+17] = uvs.x3; - positions[index+18] = uvs.y3; - - // color and alpha - var tint = sprite.tint; - colors[index+4] = colors[index+9] = colors[index+14] = colors[index+19] = (tint >> 16) + (tint & 0xff00) + ((tint & 0xff) << 16) + (sprite.worldAlpha * 255 << 24); - - // increment the batchsize - this.sprites[this.currentBatchSize++] = sprite; - - -}; - - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - // If the batch is length 0 then return as there is nothing to draw - if (this.currentBatchSize === 0) - { - return; - } - - var gl = this.renderer.gl; - var shader; - - if (this.dirty) - { - this.dirty = false; - // bind the main texture - gl.activeTexture(gl.TEXTURE0); - - // bind the buffers - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - - // this is the same for each shader? - var stride = this.vertByteSize; - gl.vertexAttribPointer(this.shader.attributes.aVertexPosition, 2, gl.FLOAT, false, stride, 0); - gl.vertexAttribPointer(this.shader.attributes.aTextureCoord, 2, gl.FLOAT, false, stride, 2 * 4); - - // color attributes will be interpreted as unsigned bytes and normalized - gl.vertexAttribPointer(this.shader.attributes.aColor, 4, gl.UNSIGNED_BYTE, true, stride, 4 * 4); - } - - // upload the verts to the buffer - if (this.currentBatchSize > ( this.size * 0.5 ) ) - { - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertices); - } - else - { - var view = this.positions.subarray(0, this.currentBatchSize * this.vertByteSize); - gl.bufferSubData(gl.ARRAY_BUFFER, 0, view); - } - - var nextTexture, nextBlendMode, nextShader; - var batchSize = 0; - var start = 0; - - var currentBaseTexture = null; - var currentBlendMode = this.renderer.blendModeManager.currentBlendMode; - var currentShader = null; - - var blendSwap = false; - var shaderSwap = false; - var sprite; - - for (var i = 0, j = this.currentBatchSize; i < j; i++) - { - - sprite = this.sprites[i]; - - nextTexture = sprite.texture.baseTexture; - nextBlendMode = sprite.blendMode; - nextShader = sprite.shader || this.shader; - - blendSwap = currentBlendMode !== nextBlendMode; - shaderSwap = currentShader !== nextShader; // should I use uuidS??? - - if (currentBaseTexture !== nextTexture || blendSwap || shaderSwap) - { - this.renderBatch(currentBaseTexture, batchSize, start); - - start = i; - batchSize = 0; - currentBaseTexture = nextTexture; - - if (blendSwap) - { - currentBlendMode = nextBlendMode; - this.renderer.blendModeManager.setBlendMode( currentBlendMode ); - } - - if (shaderSwap) - { - currentShader = nextShader; - - shader = currentShader.shaders ? currentShader.shaders[gl.id] : currentShader; - - if (!shader) - { - shader = new Shader(gl, null, currentShader.fragmentSrc, currentShader.uniforms); - currentShader.shaders[gl.id] = shader; - } - - // set shader function??? - this.renderer.shaderManager.setShader(shader); - - if (shader.dirty) - { - shader.syncUniforms(); - } - - // both thease only need to be set if they are changing.. - // set the projection - var projection = this.renderer.projection; - gl.uniform2f(shader.uniforms.projectionVector._location, projection.x, projection.y); - - // TODO - this is temprorary! - var offsetVector = this.renderer.offset; - gl.uniform2f(shader.uniforms.offsetVector._location, offsetVector.x, offsetVector.y); - - // set the pointers - } - } - - batchSize++; - } - - this.renderBatch(currentBaseTexture, batchSize, start); - - // then reset the batch! - this.currentBatchSize = 0; -}; - -/** - * @param texture {Texture} - * @param size {number} - * @param startIndex {number} - */ -SpriteRenderer.prototype.renderBatch = function (texture, size, startIndex) -{ - if (size === 0) - { - return; - } - - var gl = this.renderer.gl; - - if (!texture._glTextures[gl.id]) - { - this.renderer.updateTexture(texture); - } - else - { - // bind the current texture - gl.bindTexture(gl.TEXTURE_2D, texture._glTextures[gl.id]); - } - - // now draw those suckas! - gl.drawElements(gl.TRIANGLES, size * 6, gl.UNSIGNED_SHORT, startIndex * 6 * 2); - - // increment the draw count - this.renderer.drawCount++; -}; - -/** - * - */ -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.dirty = true; -}; - -/** - * - */ -SpriteRenderer.prototype.start = function () -{ - this.dirty = true; -}; - -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - this.renderer.gl.deleteBuffer(this.vertexBuffer); - this.renderer.gl.deleteBuffer(this.indexBuffer); - - this.vertices = null; - this.indices = null; - - this.vertexBuffer = null; - this.indexBuffer = null; - - this.currentBaseTexture = null; - - this.renderer = null; -}; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js new file mode 100644 index 0000000..8e3710a --- /dev/null +++ b/src/core/sprites/Sprite.js @@ -0,0 +1,458 @@ +var math = require('../math'), + Texture = require('../textures/Texture'), + DisplayObjectContainer = require('./DisplayObjectContainer'), + CanvasTinter = require('../renderers/canvas/utils/CanvasTinter'), + utils = require('../utils'), + CONST = require('../const'); + +/** + * The Sprite object is the base for all textured objects that are rendered to the screen + * + * A sprite can be created directly from an image like this: + * + * ```js + * var sprite = new Sprite.fromImage('assets/image.png'); + * ``` + * + * @class Sprite + * @extends DisplayObjectContainer + * @namespace PIXI + * @param texture {Texture} The texture for this sprite + */ +function Sprite(texture) +{ + DisplayObjectContainer.call(this); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting than anchor to 0.5,0.5 means the textures origin is centered + * Setting the anchor to 1,1 would mean the textures origin points will be the bottom right corner + * + * @member {Point} + */ + this.anchor = new math.Point(); + + /** + * The texture that the sprite is using + * + * @member {Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Set to CONST.blendModes.NORMAL to remove any blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {AbstractFilter} + */ + this.shader = null; + + this.renderable = true; + + // call texture setter + this.texture = texture || Texture.EMPTY; +} + +Sprite.prototype.destroy = function (destroyTexture, destroyBaseTexture) +{ + DisplayObjectContainer.prototype.destroy.call(this); + + this.anchor = null; + + if (destroyTexture) + { + this._texture.destroy(destroyBaseTexture); + } + + this._texture = null; + this.shader = null; +}; + +// constructor +Sprite.prototype = Object.create(DisplayObjectContainer.prototype); +Sprite.prototype.constructor = Sprite; +module.exports = Sprite; + +Object.defineProperties(Sprite.prototype, { + /** + * The width of the sprite, setting this will actually modify the scale to achieve the value set + * + * @member + * @memberof Sprite# + */ + width: { + get: function () + { + return this.scale.x * this.texture.frame.width; + }, + set: function (value) + { + this.scale.x = value / this.texture.frame.width; + this._width = value; + } + }, + + /** + * The height of the sprite, setting this will actually modify the scale to achieve the value set + * + * @member + * @memberof Sprite# + */ + height: { + get: function () + { + return this.scale.y * this.texture.frame.height; + }, + set: function (value) + { + this.scale.y = value / this.texture.frame.height; + this._height = value; + } + }, + + /** + * The height of the sprite, setting this will actually modify the scale to achieve the value set + * + * @member + * @memberof Sprite# + */ + texture: { + get: function () + { + return this._texture; + }, + set: function (value) + { + if (this._texture === value) + { + return; + } + + this._texture = value; + this.cachedTint = 0xFFFFFF; + + if (value) + { + // wait for the texture to load + if (value.baseTexture.hasLoaded) + { + this._onTextureUpdate(); + } + else + { + value.once('update', this._onTextureUpdate.bind(this)); + } + } + } + }, +}); + +/** + * When the texture is updated, this event will fire to update the scale and frame + * + * @private + */ +Sprite.prototype._onTextureUpdate = function () +{ + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = this._width / this.texture.frame.width; + } + + if (this._height) + { + this.scale.y = this._height / this.texture.frame.height; + } +}; + +Sprite.prototype._renderWebGL = function (renderer) +{ + renderer.setObjectRenderer(renderer.objectRenderer.sprite); + renderer.objectRenderer.sprite.render(this); +}; + +/** + * Returns the bounds of the Sprite as a rectangle. The bounds calculation takes the worldTransform into account. + * + * @param matrix {Matrix} the transformation matrix of the sprite + * @return {Rectangle} the framing rectangle + */ +Sprite.prototype.getBounds = function (matrix) +{ + var width = this.texture.frame.width; + var height = this.texture.frame.height; + + var w0 = width * (1-this.anchor.x); + var w1 = width * -this.anchor.x; + + var h0 = height * (1-this.anchor.y); + var h1 = height * -this.anchor.y; + + var worldTransform = matrix || this.worldTransform ; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var minX, + maxX, + minY, + maxY; + + if(b === 0 && c === 0) + { + // scale may be negative! + if (a < 0) + { + a *= -1; + } + + if (d < 0) + { + d *= -1; + } + + // this means there is no rotation going on right? RIGHT? + // if thats the case then we can avoid checking the bound values! yay + minX = a * w1 + tx; + maxX = a * w0 + tx; + minY = d * h1 + ty; + maxY = d * h0 + ty; + } + else + { + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + minX = x1; + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y1; + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x1; + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y1; + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + } + + var bounds = this._bounds; + + bounds.x = minX; + bounds.width = maxX - minX; + + bounds.y = minY; + bounds.height = maxY - minY; + + // store a reference so that if this function gets called again in the render cycle we do not have to recalculate + this._currentBounds = bounds; + + return bounds; +}; + +/** +* Renders the object using the Canvas renderer +* +* @param renderer {CanvasRenderer} The renderer +*/ +Sprite.prototype.renderCanvas = function (renderer) +{ + if (!this.visible || this.alpha <= 0 || this.texture.crop.width <= 0 || this.texture.crop.height <= 0) + { + return; + } + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + renderer.context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + // Ignore null sources + if (this.texture.valid) + { + var resolution = this.texture.baseTexture.resolution / renderer.resolution; + + renderer.context.globalAlpha = this.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for this texture + if (renderer.smoothProperty && renderer.scaleMode !== this.texture.baseTexture.scaleMode) + { + renderer.scaleMode = this.texture.baseTexture.scaleMode; + renderer.context[renderer.smoothProperty] = (renderer.scaleMode === CONST.scaleModes.LINEAR); + } + + // If the texture is trimmed we offset by the trim x/y, otherwise we use the frame dimensions + var dx = (this.texture.trim ? this.texture.trim.x : 0) - (this.anchor.x * this.texture.trim.width); + var dy = (this.texture.trim ? this.texture.trim.y : 0) - (this.anchor.y * this.texture.trim.height); + + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + this.worldTransform.a, + this.worldTransform.b, + this.worldTransform.c, + this.worldTransform.d, + (this.worldTransform.tx * renderer.resolution) | 0, + (this.worldTransform.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + this.worldTransform.a, + this.worldTransform.b, + this.worldTransform.c, + this.worldTransform.d, + this.worldTransform.tx * renderer.resolution, + this.worldTransform.ty * renderer.resolution + ); + } + + if (this.tint !== 0xFFFFFF) + { + if (this.cachedTint !== this.tint) + { + this.cachedTint = this.tint; + + // TODO clean up caching - how to clean up the caches? + this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); + } + + renderer.context.drawImage( + this.tintedTexture, + 0, + 0, + this.texture.crop.width, + this.texture.crop.height, + dx / resolution, + dy / resolution, + this.texture.crop.width / resolution, + this.texture.crop.height / resolution + ); + } + else + { + renderer.context.drawImage( + this.texture.baseTexture.source, + this.texture.crop.x, + this.texture.crop.y, + this.texture.crop.width, + this.texture.crop.height, + dx / resolution, + dy / resolution, + this.texture.crop.width / resolution, + this.texture.crop.height / resolution + ); + } + } + + for (var i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } +}; + +// some helper functions.. + +/** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {String} The frame Id of the texture in the cache + * @return {Sprite} A new Sprite using a texture from the texture cache matching the frameId + */ +Sprite.fromFrame = function (frameId) +{ + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache' + this); + } + + return new Sprite(texture); +}; + +/** + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded + * + * @static + * @param imageId {String} The image url of the texture + * @return {Sprite} A new Sprite using a texture from the texture cache matching the image id + */ +Sprite.fromImage = function (imageId, crossorigin, scaleMode) +{ + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); +}; diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 0669e79..c2294f9 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,7 +500,7 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.spriteRenderer.flush(); + renderer.objectRenderers.sprite.flush(); renderer.filterManager.pushFilter(this._filterBlock); } diff --git a/src/core/display/Sprite.js b/src/core/display/Sprite.js deleted file mode 100644 index e4e9d01..0000000 --- a/src/core/display/Sprite.js +++ /dev/null @@ -1,461 +0,0 @@ -var math = require('../math'), - Texture = require('../textures/Texture'), - DisplayObjectContainer = require('./DisplayObjectContainer'), - CanvasTinter = require('../renderers/canvas/utils/CanvasTinter'), - utils = require('../utils'), - CONST = require('../const'); - -/** - * The Sprite object is the base for all textured objects that are rendered to the screen - * - * A sprite can be created directly from an image like this: - * - * ```js - * var sprite = new Sprite.fromImage('assets/image.png'); - * ``` - * - * @class Sprite - * @extends DisplayObjectContainer - * @namespace PIXI - * @param texture {Texture} The texture for this sprite - */ -function Sprite(texture) -{ - DisplayObjectContainer.call(this); - - /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting than anchor to 0.5,0.5 means the textures origin is centered - * Setting the anchor to 1,1 would mean the textures origin points will be the bottom right corner - * - * @member {Point} - */ - this.anchor = new math.Point(); - - /** - * The texture that the sprite is using - * - * @member {Texture} - * @private - */ - this._texture = null; - - /** - * The width of the sprite (this is initially set by the texture) - * - * @member {number} - * @private - */ - this._width = 0; - - /** - * The height of the sprite (this is initially set by the texture) - * - * @member {number} - * @private - */ - this._height = 0; - - /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the sprite. Set to CONST.blendModes.NORMAL to remove any blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. - * - * @member {AbstractFilter} - */ - this.shader = null; - - this.renderable = true; - - // call texture setter - this.texture = texture || Texture.EMPTY; -} - -Sprite.prototype.destroy = function (destroyTexture, destroyBaseTexture) -{ - DisplayObjectContainer.prototype.destroy.call(this); - - this.anchor = null; - - if (destroyTexture) - { - this._texture.destroy(destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// constructor -Sprite.prototype = Object.create(DisplayObjectContainer.prototype); -Sprite.prototype.constructor = Sprite; -module.exports = Sprite; - -Object.defineProperties(Sprite.prototype, { - /** - * The width of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - width: { - get: function () - { - return this.scale.x * this.texture.frame.width; - }, - set: function (value) - { - this.scale.x = value / this.texture.frame.width; - this._width = value; - } - }, - - /** - * The height of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - height: { - get: function () - { - return this.scale.y * this.texture.frame.height; - }, - set: function (value) - { - this.scale.y = value / this.texture.frame.height; - this._height = value; - } - }, - - /** - * The height of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - texture: { - get: function () - { - return this._texture; - }, - set: function (value) - { - if (this._texture === value) - { - return; - } - - this._texture = value; - this.cachedTint = 0xFFFFFF; - - if (value) - { - // wait for the texture to load - if (value.baseTexture.hasLoaded) - { - this._onTextureUpdate(); - } - else - { - value.once('update', this._onTextureUpdate.bind(this)); - } - } - } - }, -}); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = this._width / this.texture.frame.width; - } - - if (this._height) - { - this.scale.y = this._height / this.texture.frame.height; - } -}; - -Sprite.prototype._renderWebGL = function (renderer) -{ - - // this is where content gets renderd.. - // watch this space for a little render state manager.. - renderer.setObjectRenderer(renderer.spriteRenderer); - renderer.spriteRenderer.render(this); -}; - -/** - * Returns the bounds of the Sprite as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param matrix {Matrix} the transformation matrix of the sprite - * @return {Rectangle} the framing rectangle - */ -Sprite.prototype.getBounds = function (matrix) -{ - var width = this.texture.frame.width; - var height = this.texture.frame.height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = matrix || this.worldTransform ; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var minX, - maxX, - minY, - maxY; - - if(b === 0 && c === 0) - { - // scale may be negative! - if (a < 0) - { - a *= -1; - } - - if (d < 0) - { - d *= -1; - } - - // this means there is no rotation going on right? RIGHT? - // if thats the case then we can avoid checking the bound values! yay - minX = a * w1 + tx; - maxX = a * w0 + tx; - minY = d * h1 + ty; - maxY = d * h0 + ty; - } - else - { - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {CanvasRenderer} The renderer -*/ -Sprite.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || this.texture.crop.width <= 0 || this.texture.crop.height <= 0) - { - return; - } - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - renderer.context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - // Ignore null sources - if (this.texture.valid) - { - var resolution = this.texture.baseTexture.resolution / renderer.resolution; - - renderer.context.globalAlpha = this.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for this texture - if (renderer.smoothProperty && renderer.scaleMode !== this.texture.baseTexture.scaleMode) - { - renderer.scaleMode = this.texture.baseTexture.scaleMode; - renderer.context[renderer.smoothProperty] = (renderer.scaleMode === CONST.scaleModes.LINEAR); - } - - // If the texture is trimmed we offset by the trim x/y, otherwise we use the frame dimensions - var dx = (this.texture.trim ? this.texture.trim.x : 0) - (this.anchor.x * this.texture.trim.width); - var dy = (this.texture.trim ? this.texture.trim.y : 0) - (this.anchor.y * this.texture.trim.height); - - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - this.worldTransform.a, - this.worldTransform.b, - this.worldTransform.c, - this.worldTransform.d, - (this.worldTransform.tx * renderer.resolution) | 0, - (this.worldTransform.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - this.worldTransform.a, - this.worldTransform.b, - this.worldTransform.c, - this.worldTransform.d, - this.worldTransform.tx * renderer.resolution, - this.worldTransform.ty * renderer.resolution - ); - } - - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - // TODO clean up caching - how to clean up the caches? - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - - renderer.context.drawImage( - this.tintedTexture, - 0, - 0, - this.texture.crop.width, - this.texture.crop.height, - dx / resolution, - dy / resolution, - this.texture.crop.width / resolution, - this.texture.crop.height / resolution - ); - } - else - { - renderer.context.drawImage( - this.texture.baseTexture.source, - this.texture.crop.x, - this.texture.crop.y, - this.texture.crop.width, - this.texture.crop.height, - dx / resolution, - dy / resolution, - this.texture.crop.width / resolution, - this.texture.crop.height / resolution - ); - } - } - - for (var i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -// some helper functions.. - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {String} The frame Id of the texture in the cache - * @return {Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache' + this); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {String} The image url of the texture - * @return {Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/display/SpriteBatch.js b/src/core/display/SpriteBatch.js deleted file mode 100644 index c196fef..0000000 --- a/src/core/display/SpriteBatch.js +++ /dev/null @@ -1,183 +0,0 @@ -var DisplayObjectContainer = require('./DisplayObjectContainer'), - WebGLFastSpriteBatch = require('../renderers/webgl/utils/WebGLFastSpriteBatch'); - -/** - * The SpriteBatch class is a really fast version of the DisplayObjectContainer built solely for speed, - * so use when you need a lot of sprites or particles. The tradeoff of the SpriteBatch is that advanced - * functionality will not work. SpriteBatch implements only the basic object transform (position, scale, rotation). - * Any other functionality like tinting, masking, etc will not work on sprites in this batch. - * - * It's extremely easy to use : - * - * ```js - * var container = new SpriteBatch(); - * - * for(var i = 0; i < 100; ++i) - * { - * var sprite = new PIXI.Sprite.fromImage("myImage.png"); - * container.addChild(sprite); - * } - * ``` - * - * And here you have a hundred sprites that will be renderer at the speed of light. - * - * @class - * @namespace PIXI - */ - -//TODO RENAME to PARTICLE CONTAINER? -function SpriteBatch() -{ - DisplayObjectContainer.call(this); -} - -SpriteBatch.prototype = Object.create(DisplayObjectContainer.prototype); -SpriteBatch.prototype.constructor = SpriteBatch; -module.exports = SpriteBatch; - -/** - * Updates the object transform for rendering - * - * @private - */ -SpriteBatch.prototype.updateTransform = function () -{ - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.DisplayObjectContainer.prototype.updateTransform.call( this ); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} The webgl renderer - * @private - */ -SpriteBatch.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || !this.children.length) - { - return; - } - - renderer.spriteBatch.stop(); - - renderer.shaderManager.setShader(renderer.shaderManager.fastShader); - - renderer.fastSpriteBatch.begin(this); - renderer.fastSpriteBatch.render(this); - - renderer.spriteBatch.start(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} The canvas renderer - * @private - */ -SpriteBatch.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || !this.children.length) - { - return; - } - - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - var frame = child.texture.frame; - - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) - { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) - { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx, - transform.ty - ); - - isRotated = false; - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5) | 0, - ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5) | 0, - frame.width * child.scale.x, - frame.height * child.scale.y - ); - } - else - { - if (!isRotated) - { - isRotated = true; - } - - child.displayObjectUpdateTransform(); - - var childTransform = child.worldTransform; - - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx | 0, - childTransform.ty | 0 - ); - } - else - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx, - childTransform.ty - ); - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - ((child.anchor.x) * (-frame.width) + 0.5) | 0, - ((child.anchor.y) * (-frame.height) + 0.5) | 0, - frame.width, - frame.height - ); - } - } -}; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js index 70d28b1..baab0f8 100644 --- a/src/core/primitives/Graphics.js +++ b/src/core/primitives/Graphics.js @@ -738,17 +738,17 @@ return; } - + */ - + if (this.glDirty) { this.dirty = true; this.glDirty = false; } - renderer.setObjectRenderer(renderer.graphicsRenderer); - renderer.graphicsRenderer.render(this); + renderer.setObjectRenderer(renderer.objectRenderers.graphics); + renderer.objectRenderers.graphics.render(this); }; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js new file mode 100644 index 0000000..55dc7a5 --- /dev/null +++ b/src/core/primitives/GraphicsRenderer.js @@ -0,0 +1,874 @@ +var utils = require('../../../utils'), + math = require('../../../math'), + CONST = require('../../../const'), + ObjectRenderer = require('../renderers/webgl/utils/ObjectRenderer'), + WebGLRenderer = require('../renderers/webgl/WebGLRenderer'), + WebGLGraphicsData = require('./WebGLGraphicsData'); + +/** + * Renders the graphics object. + * + * @class + * @private + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this object renderer works for. + */ +function GraphicsRenderer(renderer) +{ + ObjectRenderer.call(this, renderer); + + this.graphicsDataPool = []; +} + +GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); +GraphicsRenderer.prototype.constructor = GraphicsRenderer; +module.exports = GraphicsRenderer; + +WebGLRenderer.registerObjectRenderer('graphics', GraphicsRenderer); + +/** + * Destroys this renderer. + * + */ +GraphicsRenderer.prototype.destroy = function () { + ObjectRenderer.prototype.destroy.call(this); + + this.graphicsDataPool = null; +}; + +/** + * Renders a graphics object. + * + * @param graphics {Graphics} The graphics object to render. + */ +GraphicsRenderer.prototype.render = function(graphics) +{ + var renderer = this.renderer; + var gl = renderer.gl; + + var projection = renderer.projection, + offset = renderer.offset, + shader = renderer.shaderManager.primitiveShader, + webGLData; + + if (graphics.dirty) + { + this.updateGraphics(graphics, gl); + } + + var webGL = graphics._webGL[gl.id]; + + // This could be speeded up for sure! + + renderer.blendModeManager.setBlendMode( graphics.blendMode ); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.stencilManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.stencilManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.primitiveShader; + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniform1f(shader.uniforms.flipY._location, 1); + + gl.uniform2f(shader.uniforms.projectionVector._location, projection.x, -projection.y); + gl.uniform2f(shader.uniforms.offsetVector._location, -offset.x, -offset.y); + + gl.uniform3fv(shader.uniforms.tint._location, utils.hex2rgb(graphics.tint)); + + gl.uniform1f(shader.uniforms.alpha._location, graphics.worldAlpha); + + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.attributes.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); + gl.vertexAttribPointer(shader.attributes.aColor, 4, gl.FLOAT, false,4 * 6, 2 * 4); + + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + } + } +} + +/** + * Updates the graphics object + * + * @private + * @param graphicsData {Graphics} The graphics object to update + * @param gl {WebGLContext} the current WebGL drawing context + */ +GraphicsRenderer.prototype.updateGraphics = function(graphics) +{ + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[gl.id]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[gl.id] = {lastIndex:0, data:[], gl:gl}; + } + + // flag the graphics as not dirty as we are about to update it... + graphics.dirty = false; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty) + { + graphics.clearDirty = false; + + // lop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + graphicsData.reset(); + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + if (data.type === CONST.SHAPES.POLY) + { + // need to add the points the the graphics object.. + data.points = data.shape.points.slice(); + if (data.shape.closed) + { + // close the poly if the value is true! + if (data.points[0] !== data.points[data.points.length-2] || data.points[1] !== data.points[data.points.length-1]) + { + data.points.push(data.points[0], data.points[1]); + } + } + + // MAKE SURE WE HAVE THE CORRECT TYPE.. + if (data.fill) + { + if (data.points.length >= 6) + { + if (data.points.length < 6 * 2) + { + webGLData = this.switchMode(webGL, 0); + + var canDrawUsingSimple = this.buildPoly(data, webGLData); + // console.log(canDrawUsingSimple); + + if (!canDrawUsingSimple) + { + // console.log("<>>>") + webGLData = this.switchMode(webGL, 1); + this.buildComplexPoly(data, webGLData); + } + + } + else + { + webGLData = this.switchMode(webGL, 1); + this.buildComplexPoly(data, webGLData); + } + } + } + + if (data.lineWidth > 0) + { + webGLData = this.switchMode(webGL, 0); + this.buildLine(data, webGLData); + } + } + else + { + webGLData = this.switchMode(webGL, 0); + + if (data.type === CONST.SHAPES.RECT) + { + this.buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + this.buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + this.buildRoundedRectangle(data, webGLData); + } + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } +} + +/** + * + * + * @private + * @param webGL {WebGLContext} + * @param type {number} + */ +GraphicsRenderer.prototype.switchMode = function (webGL, type) +{ + var webGLData; + + if (!webGL.data.length) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(webGL.gl); + webGLData.mode = type; + webGL.data.push(webGLData); + } + else + { + webGLData = webGL.data[webGL.data.length-1]; + + if (webGLData.mode !== type || type === 1) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(webGL.gl); + webGLData.mode = type; + webGL.data.push(webGLData); + } + } + + webGLData.dirty = true; + + return webGLData; +}; + +/** + * Builds a rectangle to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildRectangle = function (graphicsData, webGLData) +{ + // --- // + // need to convert points to a nice regular data + // + var rectData = graphicsData.shape; + var x = rectData.x; + var y = rectData.y; + var width = rectData.width; + var height = rectData.height; + + if (graphicsData.fill) + { + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var verts = webGLData.points; + var indices = webGLData.indices; + + var vertPos = verts.length/6; + + // start + verts.push(x, y); + verts.push(r, g, b, alpha); + + verts.push(x + width, y); + verts.push(r, g, b, alpha); + + verts.push(x , y + height); + verts.push(r, g, b, alpha); + + verts.push(x + width, y + height); + verts.push(r, g, b, alpha); + + // insert 2 dead triangles.. + indices.push(vertPos, vertPos, vertPos+1, vertPos+2, vertPos+3, vertPos+3); + } + + if (graphicsData.lineWidth) + { + var tempPoints = graphicsData.points; + + graphicsData.points = [x, y, + x + width, y, + x + width, y + height, + x, y + height, + x, y]; + + + this.buildLine(graphicsData, webGLData); + + graphicsData.points = tempPoints; + } +}; + +/** + * Builds a rounded rectangle to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildRoundedRectangle = function (graphicsData, webGLData) +{ + var rrectData = graphicsData.shape; + var x = rrectData.x; + var y = rrectData.y; + var width = rrectData.width; + var height = rrectData.height; + + var radius = rrectData.radius; + + var recPoints = []; + recPoints.push(x, y + radius); + recPoints = recPoints.concat(this.quadraticBezierCurve(x, y + height - radius, x, y + height, x + radius, y + height)); + recPoints = recPoints.concat(this.quadraticBezierCurve(x + width - radius, y + height, x + width, y + height, x + width, y + height - radius)); + recPoints = recPoints.concat(this.quadraticBezierCurve(x + width, y + radius, x + width, y, x + width - radius, y)); + recPoints = recPoints.concat(this.quadraticBezierCurve(x + radius, y, x, y, x, y + radius)); + + if (graphicsData.fill) + { + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var verts = webGLData.points; + var indices = webGLData.indices; + + var vecPos = verts.length/6; + + var triangles = utils.PolyK.Triangulate(recPoints); + + // + + var i = 0; + for (i = 0; i < triangles.length; i+=3) + { + indices.push(triangles[i] + vecPos); + indices.push(triangles[i] + vecPos); + indices.push(triangles[i+1] + vecPos); + indices.push(triangles[i+2] + vecPos); + indices.push(triangles[i+2] + vecPos); + } + + for (i = 0; i < recPoints.length; i++) + { + verts.push(recPoints[i], recPoints[++i], r, g, b, alpha); + } + } + + if (graphicsData.lineWidth) + { + var tempPoints = graphicsData.points; + + graphicsData.points = recPoints; + + this.buildLine(graphicsData, webGLData); + + graphicsData.points = tempPoints; + } +}; + +/** + * Calculate the points for a quadratic bezier curve. (helper function..) + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @private + * @param fromX {number} Origin point x + * @param fromY {number} Origin point x + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {number[]} + */ +GraphicsRenderer.prototype.quadraticBezierCurve = function (fromX, fromY, cpX, cpY, toX, toY) +{ + + var xa, + ya, + xb, + yb, + x, + y, + n = 20, + points = []; + + function getPt(n1 , n2, perc) { + var diff = n2 - n1; + + return n1 + ( diff * perc ); + } + + var j = 0; + for (var i = 0; i <= n; i++ ) { + j = i / n; + + // The Green Line + xa = getPt( fromX , cpX , j ); + ya = getPt( fromY , cpY , j ); + xb = getPt( cpX , toX , j ); + yb = getPt( cpY , toY , j ); + + // The Black Dot + x = getPt( xa , xb , j ); + y = getPt( ya , yb , j ); + + points.push(x, y); + } + return points; +}; + +/** + * Builds a circle to draw + * + * @private + * @param graphicsData {Graphics} The graphics object to draw + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildCircle = function (graphicsData, webGLData) +{ + // need to convert points to a nice regular data + var circleData = graphicsData.shape; + var x = circleData.x; + var y = circleData.y; + var width; + var height; + + // TODO - bit hacky?? + if (graphicsData.type === CONST.SHAPES.CIRC) + { + width = circleData.radius; + height = circleData.radius; + } + else + { + width = circleData.width; + height = circleData.height; + } + + var totalSegs = 40; + var seg = (Math.PI * 2) / totalSegs ; + + var i = 0; + + if (graphicsData.fill) + { + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var verts = webGLData.points; + var indices = webGLData.indices; + + var vecPos = verts.length/6; + + indices.push(vecPos); + + for (i = 0; i < totalSegs + 1 ; i++) + { + verts.push(x,y, r, g, b, alpha); + + verts.push(x + Math.sin(seg * i) * width, + y + Math.cos(seg * i) * height, + r, g, b, alpha); + + indices.push(vecPos++, vecPos++); + } + + indices.push(vecPos-1); + } + + if (graphicsData.lineWidth) + { + var tempPoints = graphicsData.points; + + graphicsData.points = []; + + for (i = 0; i < totalSegs + 1; i++) + { + graphicsData.points.push(x + Math.sin(seg * i) * width, + y + Math.cos(seg * i) * height); + } + + this.buildLine(graphicsData, webGLData); + + graphicsData.points = tempPoints; + } +}; + +/** + * Builds a line to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildLine = function (graphicsData, webGLData) +{ + // TODO OPTIMISE! + var i = 0; + var points = graphicsData.points; + + if (points.length === 0) + { + return; + } + + // if the line width is an odd number add 0.5 to align to a whole pixel + if (graphicsData.lineWidth%2) + { + for (i = 0; i < points.length; i++) + { + points[i] += 0.5; + } + } + + // get first and last point.. figure out the middle! + var firstPoint = new math.Point(points[0], points[1]); + var lastPoint = new math.Point(points[points.length - 2], points[points.length - 1]); + + // if the first point is the last point - gonna have issues :) + if (firstPoint.x === lastPoint.x && firstPoint.y === lastPoint.y) + { + // need to clone as we are going to slightly modify the shape.. + points = points.slice(); + + points.pop(); + points.pop(); + + lastPoint = new math.Point(points[points.length - 2], points[points.length - 1]); + + var midPointX = lastPoint.x + (firstPoint.x - lastPoint.x) *0.5; + var midPointY = lastPoint.y + (firstPoint.y - lastPoint.y) *0.5; + + points.unshift(midPointX, midPointY); + points.push(midPointX, midPointY); + } + + var verts = webGLData.points; + var indices = webGLData.indices; + var length = points.length / 2; + var indexCount = points.length; + var indexStart = verts.length/6; + + // DRAW the Line + var width = graphicsData.lineWidth / 2; + + // sort color + var color = utils.hex2rgb(graphicsData.lineColor); + var alpha = graphicsData.lineAlpha; + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var px, py, p1x, p1y, p2x, p2y, p3x, p3y; + var perpx, perpy, perp2x, perp2y, perp3x, perp3y; + var a1, b1, c1, a2, b2, c2; + var denom, pdist, dist; + + p1x = points[0]; + p1y = points[1]; + + p2x = points[2]; + p2y = points[3]; + + perpx = -(p1y - p2y); + perpy = p1x - p2x; + + dist = Math.sqrt(perpx*perpx + perpy*perpy); + + perpx /= dist; + perpy /= dist; + perpx *= width; + perpy *= width; + + // start + verts.push(p1x - perpx , p1y - perpy, + r, g, b, alpha); + + verts.push(p1x + perpx , p1y + perpy, + r, g, b, alpha); + + for (i = 1; i < length-1; i++) + { + p1x = points[(i-1)*2]; + p1y = points[(i-1)*2 + 1]; + + p2x = points[(i)*2]; + p2y = points[(i)*2 + 1]; + + p3x = points[(i+1)*2]; + p3y = points[(i+1)*2 + 1]; + + perpx = -(p1y - p2y); + perpy = p1x - p2x; + + dist = Math.sqrt(perpx*perpx + perpy*perpy); + perpx /= dist; + perpy /= dist; + perpx *= width; + perpy *= width; + + perp2x = -(p2y - p3y); + perp2y = p2x - p3x; + + dist = Math.sqrt(perp2x*perp2x + perp2y*perp2y); + perp2x /= dist; + perp2y /= dist; + perp2x *= width; + perp2y *= width; + + a1 = (-perpy + p1y) - (-perpy + p2y); + b1 = (-perpx + p2x) - (-perpx + p1x); + c1 = (-perpx + p1x) * (-perpy + p2y) - (-perpx + p2x) * (-perpy + p1y); + a2 = (-perp2y + p3y) - (-perp2y + p2y); + b2 = (-perp2x + p2x) - (-perp2x + p3x); + c2 = (-perp2x + p3x) * (-perp2y + p2y) - (-perp2x + p2x) * (-perp2y + p3y); + + denom = a1*b2 - a2*b1; + + if (Math.abs(denom) < 0.1 ) + { + + denom+=10.1; + verts.push(p2x - perpx , p2y - perpy, + r, g, b, alpha); + + verts.push(p2x + perpx , p2y + perpy, + r, g, b, alpha); + + continue; + } + + px = (b1*c2 - b2*c1)/denom; + py = (a2*c1 - a1*c2)/denom; + + + pdist = (px -p2x) * (px -p2x) + (py -p2y) + (py -p2y); + + + if (pdist > 140 * 140) + { + perp3x = perpx - perp2x; + perp3y = perpy - perp2y; + + dist = Math.sqrt(perp3x*perp3x + perp3y*perp3y); + perp3x /= dist; + perp3y /= dist; + perp3x *= width; + perp3y *= width; + + verts.push(p2x - perp3x, p2y -perp3y); + verts.push(r, g, b, alpha); + + verts.push(p2x + perp3x, p2y +perp3y); + verts.push(r, g, b, alpha); + + verts.push(p2x - perp3x, p2y -perp3y); + verts.push(r, g, b, alpha); + + indexCount++; + } + else + { + + verts.push(px , py); + verts.push(r, g, b, alpha); + + verts.push(p2x - (px-p2x), p2y - (py - p2y)); + verts.push(r, g, b, alpha); + } + } + + p1x = points[(length-2)*2]; + p1y = points[(length-2)*2 + 1]; + + p2x = points[(length-1)*2]; + p2y = points[(length-1)*2 + 1]; + + perpx = -(p1y - p2y); + perpy = p1x - p2x; + + dist = Math.sqrt(perpx*perpx + perpy*perpy); + perpx /= dist; + perpy /= dist; + perpx *= width; + perpy *= width; + + verts.push(p2x - perpx , p2y - perpy); + verts.push(r, g, b, alpha); + + verts.push(p2x + perpx , p2y + perpy); + verts.push(r, g, b, alpha); + + indices.push(indexStart); + + for (i = 0; i < indexCount; i++) + { + indices.push(indexStart++); + } + + indices.push(indexStart-1); +}; + +/** + * Builds a complex polygon to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildComplexPoly = function (graphicsData, webGLData) +{ + //TODO - no need to copy this as it gets turned into a FLoat32Array anyways.. + var points = graphicsData.points.slice(); + + if (points.length < 6) + { + return; + } + + // get first and last point.. figure out the middle! + var indices = webGLData.indices; + webGLData.points = points; + webGLData.alpha = graphicsData.fillAlpha; + webGLData.color = utils.hex2rgb(graphicsData.fillColor); + + // calclate the bounds.. + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + var x,y; + + // get size.. + for (var i = 0; i < points.length; i+=2) + { + x = points[i]; + y = points[i+1]; + + minX = x < minX ? x : minX; + maxX = x > maxX ? x : maxX; + + minY = y < minY ? y : minY; + maxY = y > maxY ? y : maxY; + } + + // add a quad to the end cos there is no point making another buffer! + points.push(minX, minY, + maxX, minY, + maxX, maxY, + minX, maxY); + + // push a quad onto the end.. + + //TODO - this aint needed! + var length = points.length / 2; + for (i = 0; i < length; i++) + { + indices.push( i ); + } + +}; + +/** + * Builds a polygon to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildPoly = function (graphicsData, webGLData) +{ + var points = graphicsData.points; + + if (points.length < 6) + { + return; + } + + // get first and last point.. figure out the middle! + var verts = webGLData.points; + var indices = webGLData.indices; + + var length = points.length / 2; + + // sort color + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var triangles = utils.PolyK.Triangulate(points); + + if (!triangles) { + return false; + } + + var vertPos = verts.length / 6; + + var i = 0; + + for (i = 0; i < triangles.length; i+=3) + { + indices.push(triangles[i] + vertPos); + indices.push(triangles[i] + vertPos); + indices.push(triangles[i+1] + vertPos); + indices.push(triangles[i+2] +vertPos); + indices.push(triangles[i+2] + vertPos); + } + + for (i = 0; i < length; i++) + { + verts.push(points[i * 2], points[i * 2 + 1], + r, g, b, alpha); + } + + return true; +}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 547bdc7..b0189c5 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,6 +1,4 @@ -var SpriteRenderer = require('./utils/SpriteRenderer'), - GraphicsRenderer = require('./utils/GraphicsRenderer'), - WebGLFastSpriteBatch = require('./utils/WebGLFastSpriteBatch'), +var WebGLFastSpriteBatch = require('./utils/WebGLFastSpriteBatch'), WebGLShaderManager = require('./managers/WebGLShaderManager'), WebGLMaskManager = require('./managers/WebGLMaskManager'), WebGLFilterManager = require('./managers/WebGLFilterManager'), @@ -28,21 +26,21 @@ * @param [options.preserveDrawingBuffer=false] {boolean} enables drawing buffer preservation, enable this if you need to call toDataUrl on the webgl context * @param [options.resolution=1] {number} the resolution of the renderer retina would be 2 */ -function WebGLRenderer(width, height, options) +function WebGLRenderer(width, height, options) { utils.sayHello('webGL'); - if (options) + if (options) { - for (var i in CONST.defaultRenderOptions) + for (var i in CONST.defaultRenderOptions) { - if (typeof options[i] === 'undefined') + if (typeof options[i] === 'undefined') { options[i] = CONST.defaultRenderOptions[i]; } } } - else + else { options = CONST.defaultRenderOptions; } @@ -191,14 +189,6 @@ /** * Manages the rendering of sprites - * @member {SpriteRenderer} - */ - this.spriteRenderer = new SpriteRenderer(this); - - this.graphicsRenderer = new GraphicsRenderer(this); - - /** - * Manages the rendering of sprites * @member {WebGLFastSpriteBatch} */ this.fastSpriteBatch = new WebGLFastSpriteBatch(this); @@ -245,8 +235,17 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - this.currentRenderer = this.spriteRenderer; + /** + * Manages the renderer of specific objects. + * + * @member {object} + */ + this.objectRenderers = {}; + // create an instance of each registered object renderer + for (var o in WebGLRenderer._objectRenderers) { + this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); + } } // constructor @@ -255,21 +254,20 @@ utils.eventTarget.mixin(WebGLRenderer.prototype); -Object.defineProperties(WebGLRenderer.prototype, -{ +Object.defineProperties(WebGLRenderer.prototype, { /** * The background color to fill if not transparent * * @member {number} * @memberof WebGLRenderer# */ - backgroundColor: + backgroundColor: { - get: function () + get: function () { return this._backgroundColor; }, - set: function (val) + set: function (val) { this._backgroundColor = val; utils.hex2rgb(val, this._backgroundColorRgb); @@ -281,12 +279,12 @@ * * @private */ -WebGLRenderer.prototype._initContext = function () +WebGLRenderer.prototype._initContext = function () { var gl = this.view.getContext('webgl', this._contextOptions) || this.view.getContext('experimental-webgl', this._contextOptions); this.gl = gl; - if (!gl) + if (!gl) { // fail, not able to get a context throw new Error('This browser does not support webGL. Try using the canvas renderer'); @@ -312,10 +310,10 @@ * * @param object {DisplayObject} the object to be rendered */ -WebGLRenderer.prototype.render = function (object) +WebGLRenderer.prototype.render = function (object) { // no point rendering if our context has been blown up! - if (this.gl.isContextLost()) + if (this.gl.isContextLost()) { return; } @@ -336,13 +334,13 @@ // make sure we are bound to the main frame buffer gl.bindFramebuffer(gl.FRAMEBUFFER, null); - if (this.clearBeforeRender) + if (this.clearBeforeRender) { - if (this.transparent) + if (this.transparent) { gl.clearColor(0, 0, 0, 0); } - else + else { gl.clearColor(this._backgroundColorRgb[0], this._backgroundColorRgb[1], this._backgroundColorRgb[2], 1); } @@ -360,7 +358,7 @@ * @param projection {Point} The projection * @param buffer {Array} a standard WebGL buffer */ -WebGLRenderer.prototype.renderDisplayObject = function (displayObject, projection, buffer) +WebGLRenderer.prototype.renderDisplayObject = function (displayObject, projection, buffer) { this.blendModeManager.setBlendMode(CONST.blendModes.NORMAL); @@ -386,9 +384,9 @@ this.currentRenderer.flush(); }; -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) +WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) { - if(this.currentRenderer === objectRenderer) + if (this.currentRenderer === objectRenderer) { return; } @@ -396,7 +394,7 @@ this.currentRenderer.stop(); this.currentRenderer = objectRenderer; this.currentRenderer.start(); -} +}; /** * Resizes the webGL view to the specified width and height. @@ -404,7 +402,7 @@ * @param width {number} the new width of the webGL view * @param height {number} the new height of the webGL view */ -WebGLRenderer.prototype.resize = function (width, height) +WebGLRenderer.prototype.resize = function (width, height) { this.width = width * this.resolution; this.height = height * this.resolution; @@ -412,7 +410,7 @@ this.view.width = this.width; this.view.height = this.height; - if (this.autoResize) + if (this.autoResize) { this.view.style.width = this.width / this.resolution + 'px'; this.view.style.height = this.height / this.resolution + 'px'; @@ -429,18 +427,18 @@ * * @param texture {BaseTexture|Texture} the texture to update */ -WebGLRenderer.prototype.updateTexture = function (texture) +WebGLRenderer.prototype.updateTexture = function (texture) { texture = texture.baseTexture || texture; - if (!texture.hasLoaded) + if (!texture.hasLoaded) { return; } var gl = this.gl; - if (!texture._glTextures[gl.id]) + if (!texture._glTextures[gl.id]) { texture._glTextures[gl.id] = gl.createTexture(); texture.on('update', this._boundUpdateTexture); @@ -455,22 +453,22 @@ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, texture.scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); - if (texture.mipmap && utils.isPowerOfTwo(texture.width, texture.height)) + if (texture.mipmap && utils.isPowerOfTwo(texture.width, texture.height)) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); gl.generateMipmap(gl.TEXTURE_2D); } - else + else { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); } - if (!texture._powerOf2) + if (!texture._powerOf2) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); } - else + else { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); @@ -479,16 +477,16 @@ return texture._glTextures[gl.id]; }; -WebGLRenderer.prototype.destroyTexture = function (texture) +WebGLRenderer.prototype.destroyTexture = function (texture) { texture = texture.baseTexture || texture; - if (!texture.hasLoaded) + if (!texture.hasLoaded) { return; } - if (texture._glTextures[this.gl.id]) + if (texture._glTextures[this.gl.id]) { this.gl.deleteTexture(texture._glTextures[this.gl.id]); } @@ -500,7 +498,7 @@ * @param event {Event} * @private */ -WebGLRenderer.prototype.handleContextLost = function (event) +WebGLRenderer.prototype.handleContextLost = function (event) { event.preventDefault(); }; @@ -511,12 +509,12 @@ * @param event {Event} * @private */ -WebGLRenderer.prototype.handleContextRestored = function () +WebGLRenderer.prototype.handleContextRestored = function () { this._initContext(); // empty all the ol gl textures as they are useless now - for (var key in utils.TextureCache) + for (var key in utils.TextureCache) { var texture = utils.TextureCache[key].baseTexture; texture._glTextures = []; @@ -528,9 +526,9 @@ * * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ -WebGLRenderer.prototype.destroy = function (removeView) +WebGLRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parent) + if (removeView && this.view.parent) { this.view.parent.removeChild(this.view); } @@ -541,10 +539,15 @@ // time to create the render managers! each one focuses on managine a state in webGL this.shaderManager.destroy(); - this.spriteRenderer.destroy(); this.maskManager.destroy(); this.filterManager.destroy(); + for (var o in this.objectRenderers) { + this.objectRenderers[o].destroy(); + this.objectRenderers[o] = null; + } + + this.objectRenderers = null; // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -574,7 +577,6 @@ this.drawCount = 0; this.shaderManager = null; - this.spriteRenderer = null; this.maskManager = null; this.filterManager = null; this.stencilManager = null; @@ -591,11 +593,11 @@ * * @private */ -WebGLRenderer.prototype._mapBlendModes = function () +WebGLRenderer.prototype._mapBlendModes = function () { var gl = this.gl; - if (!this.blendModes) + if (!this.blendModes) { this.blendModes = {}; @@ -620,3 +622,9 @@ }; WebGLRenderer.glContextId = 0; +WebGLRenderer._objectRenderers = {}; + +WebGLRenderer.registerObjectRenderer = function (name, ctor) { + WebGLRenderer._objectRenderers[name] = ctor; +}; + diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js index 773cc5d..e81ca56 100644 --- a/src/core/renderers/webgl/managers/WebGLMaskManager.js +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -20,11 +20,11 @@ */ WebGLMaskManager.prototype.pushMask = function (maskData) { - this.renderer.setObjectRenderer( this.renderer.graphicsRenderer ); + this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); if (maskData.dirty) { - this.renderer.graphicsRenderer.updateGraphics(maskData, this.renderer.gl); + this.renderer.objectRenderers.graphics.updateGraphics(maskData, this.renderer.gl); } if (!maskData._webGL[this.renderer.gl.id].data.length) @@ -42,7 +42,7 @@ */ WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.setObjectRenderer( this.renderer.graphicsRenderer ); - + this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.stencilManager.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); }; diff --git a/src/core/renderers/webgl/utils/GraphicsRenderer.js b/src/core/renderers/webgl/utils/GraphicsRenderer.js deleted file mode 100644 index 6713275..0000000 --- a/src/core/renderers/webgl/utils/GraphicsRenderer.js +++ /dev/null @@ -1,894 +0,0 @@ -var utils = require('../../../utils'), - math = require('../../../math'), - CONST = require('../../../const'), - WebGLGraphicsData = require('./WebGLGraphicsData'); - -/** - * A set of functions used by the webGL renderer to draw the primitive graphics data - * - * @namespace PIXI - * @private - */ -var WebGLGraphics = module.exports = {}; - -/** - * Renders the graphics object - * - * @static - * @private - * @param graphics {Graphics} - * @param renderer {WebGLRenderer} - */ - -var GraphicsRenderer = function(renderer) -{ - this.renderer = renderer; - this.graphicsDataPool = []; -} - -module.exports = GraphicsRenderer; - -GraphicsRenderer.prototype.start = function() -{ - // set the shader.. - -} - -GraphicsRenderer.prototype.stop = function() -{ - // flush! -} - -GraphicsRenderer.prototype.flush = function() -{ - // flush! -} - - - -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var projection = renderer.projection, - offset = renderer.offset, - shader = renderer.shaderManager.primitiveShader, - webGLData; - - if (graphics.dirty) - { - this.updateGraphics(graphics, gl); - } - - var webGL = graphics._webGL[gl.id]; - - // This could be speeded up for sure! - - renderer.blendModeManager.setBlendMode( graphics.blendMode ); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.stencilManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.stencilManager.popStencil(graphics, webGLData, renderer); - } - else - { - webGLData = webGL.data[i]; - - - renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); - shader = renderer.shaderManager.primitiveShader; - gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); - - gl.uniform1f(shader.uniforms.flipY._location, 1); - - gl.uniform2f(shader.uniforms.projectionVector._location, projection.x, -projection.y); - gl.uniform2f(shader.uniforms.offsetVector._location, -offset.x, -offset.y); - - gl.uniform3fv(shader.uniforms.tint._location, utils.hex2rgb(graphics.tint)); - - gl.uniform1f(shader.uniforms.alpha._location, graphics.worldAlpha); - - - gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); - - gl.vertexAttribPointer(shader.attributes.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); - gl.vertexAttribPointer(shader.attributes.aColor, 4, gl.FLOAT, false,4 * 6, 2 * 4); - - // set the index buffer! - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); - gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); - } - } -} - -/** - * Updates the graphics object - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object to update - * @param gl {WebGLContext} the current WebGL drawing context - */ - -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[gl.id]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[gl.id] = {lastIndex:0, data:[], gl:gl}; - } - - // flag the graphics as not dirty as we are about to update it... - graphics.dirty = false; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty) - { - graphics.clearDirty = false; - - // lop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - graphicsData.reset(); - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - if (data.type === CONST.SHAPES.POLY) - { - // need to add the points the the graphics object.. - data.points = data.shape.points.slice(); - if (data.shape.closed) - { - // close the poly if the value is true! - if (data.points[0] !== data.points[data.points.length-2] || data.points[1] !== data.points[data.points.length-1]) - { - data.points.push(data.points[0], data.points[1]); - } - } - - // MAKE SURE WE HAVE THE CORRECT TYPE.. - if (data.fill) - { - if (data.points.length >= 6) - { - if (data.points.length < 6 * 2) - { - webGLData = this.switchMode(webGL, 0); - - var canDrawUsingSimple = this.buildPoly(data, webGLData); - // console.log(canDrawUsingSimple); - - if (!canDrawUsingSimple) - { - // console.log("<>>>") - webGLData = this.switchMode(webGL, 1); - this.buildComplexPoly(data, webGLData); - } - - } - else - { - webGLData = this.switchMode(webGL, 1); - this.buildComplexPoly(data, webGLData); - } - } - } - - if (data.lineWidth > 0) - { - webGLData = this.switchMode(webGL, 0); - this.buildLine(data, webGLData); - } - } - else - { - webGLData = this.switchMode(webGL, 0); - - if (data.type === CONST.SHAPES.RECT) - { - this.buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - this.buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - this.buildRoundedRectangle(data, webGLData); - } - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -} - -/** - * @static - * @private - * @param webGL {WebGLContext} - * @param type {number} - */ -GraphicsRenderer.prototype.switchMode = function (webGL, type) -{ - var webGLData; - - if (!webGL.data.length) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(webGL.gl); - webGLData.mode = type; - webGL.data.push(webGLData); - } - else - { - webGLData = webGL.data[webGL.data.length-1]; - - if (webGLData.mode !== type || type === 1) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(webGL.gl); - webGLData.mode = type; - webGL.data.push(webGLData); - } - } - - webGLData.dirty = true; - - return webGLData; -}; - - - - - - -/** - * Builds a rectangle to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildRectangle = function (graphicsData, webGLData) -{ - // --- // - // need to convert points to a nice regular data - // - var rectData = graphicsData.shape; - var x = rectData.x; - var y = rectData.y; - var width = rectData.width; - var height = rectData.height; - - if (graphicsData.fill) - { - var color = utils.hex2rgb(graphicsData.fillColor); - var alpha = graphicsData.fillAlpha; - - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var verts = webGLData.points; - var indices = webGLData.indices; - - var vertPos = verts.length/6; - - // start - verts.push(x, y); - verts.push(r, g, b, alpha); - - verts.push(x + width, y); - verts.push(r, g, b, alpha); - - verts.push(x , y + height); - verts.push(r, g, b, alpha); - - verts.push(x + width, y + height); - verts.push(r, g, b, alpha); - - // insert 2 dead triangles.. - indices.push(vertPos, vertPos, vertPos+1, vertPos+2, vertPos+3, vertPos+3); - } - - if (graphicsData.lineWidth) - { - var tempPoints = graphicsData.points; - - graphicsData.points = [x, y, - x + width, y, - x + width, y + height, - x, y + height, - x, y]; - - - this.buildLine(graphicsData, webGLData); - - graphicsData.points = tempPoints; - } -}; - -/** - * Builds a rounded rectangle to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildRoundedRectangle = function (graphicsData, webGLData) -{ - var rrectData = graphicsData.shape; - var x = rrectData.x; - var y = rrectData.y; - var width = rrectData.width; - var height = rrectData.height; - - var radius = rrectData.radius; - - var recPoints = []; - recPoints.push(x, y + radius); - recPoints = recPoints.concat(this.quadraticBezierCurve(x, y + height - radius, x, y + height, x + radius, y + height)); - recPoints = recPoints.concat(this.quadraticBezierCurve(x + width - radius, y + height, x + width, y + height, x + width, y + height - radius)); - recPoints = recPoints.concat(this.quadraticBezierCurve(x + width, y + radius, x + width, y, x + width - radius, y)); - recPoints = recPoints.concat(this.quadraticBezierCurve(x + radius, y, x, y, x, y + radius)); - - if (graphicsData.fill) - { - var color = utils.hex2rgb(graphicsData.fillColor); - var alpha = graphicsData.fillAlpha; - - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var verts = webGLData.points; - var indices = webGLData.indices; - - var vecPos = verts.length/6; - - var triangles = utils.PolyK.Triangulate(recPoints); - - // - - var i = 0; - for (i = 0; i < triangles.length; i+=3) - { - indices.push(triangles[i] + vecPos); - indices.push(triangles[i] + vecPos); - indices.push(triangles[i+1] + vecPos); - indices.push(triangles[i+2] + vecPos); - indices.push(triangles[i+2] + vecPos); - } - - for (i = 0; i < recPoints.length; i++) - { - verts.push(recPoints[i], recPoints[++i], r, g, b, alpha); - } - } - - if (graphicsData.lineWidth) - { - var tempPoints = graphicsData.points; - - graphicsData.points = recPoints; - - this.buildLine(graphicsData, webGLData); - - graphicsData.points = tempPoints; - } -}; - -/** - * Calculate the points for a quadratic bezier curve. (helper function..) - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @static - * @private - * @param fromX {number} Origin point x - * @param fromY {number} Origin point x - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {number[]} - */ -GraphicsRenderer.prototype.quadraticBezierCurve = function (fromX, fromY, cpX, cpY, toX, toY) -{ - - var xa, - ya, - xb, - yb, - x, - y, - n = 20, - points = []; - - function getPt(n1 , n2, perc) { - var diff = n2 - n1; - - return n1 + ( diff * perc ); - } - - var j = 0; - for (var i = 0; i <= n; i++ ) { - j = i / n; - - // The Green Line - xa = getPt( fromX , cpX , j ); - ya = getPt( fromY , cpY , j ); - xb = getPt( cpX , toX , j ); - yb = getPt( cpY , toY , j ); - - // The Black Dot - x = getPt( xa , xb , j ); - y = getPt( ya , yb , j ); - - points.push(x, y); - } - return points; -}; - -/** - * Builds a circle to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object to draw - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildCircle = function (graphicsData, webGLData) -{ - // need to convert points to a nice regular data - var circleData = graphicsData.shape; - var x = circleData.x; - var y = circleData.y; - var width; - var height; - - // TODO - bit hacky?? - if (graphicsData.type === CONST.SHAPES.CIRC) - { - width = circleData.radius; - height = circleData.radius; - } - else - { - width = circleData.width; - height = circleData.height; - } - - var totalSegs = 40; - var seg = (Math.PI * 2) / totalSegs ; - - var i = 0; - - if (graphicsData.fill) - { - var color = utils.hex2rgb(graphicsData.fillColor); - var alpha = graphicsData.fillAlpha; - - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var verts = webGLData.points; - var indices = webGLData.indices; - - var vecPos = verts.length/6; - - indices.push(vecPos); - - for (i = 0; i < totalSegs + 1 ; i++) - { - verts.push(x,y, r, g, b, alpha); - - verts.push(x + Math.sin(seg * i) * width, - y + Math.cos(seg * i) * height, - r, g, b, alpha); - - indices.push(vecPos++, vecPos++); - } - - indices.push(vecPos-1); - } - - if (graphicsData.lineWidth) - { - var tempPoints = graphicsData.points; - - graphicsData.points = []; - - for (i = 0; i < totalSegs + 1; i++) - { - graphicsData.points.push(x + Math.sin(seg * i) * width, - y + Math.cos(seg * i) * height); - } - - this.buildLine(graphicsData, webGLData); - - graphicsData.points = tempPoints; - } -}; - -/** - * Builds a line to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildLine = function (graphicsData, webGLData) -{ - // TODO OPTIMISE! - var i = 0; - var points = graphicsData.points; - - if (points.length === 0) - { - return; - } - - // if the line width is an odd number add 0.5 to align to a whole pixel - if (graphicsData.lineWidth%2) - { - for (i = 0; i < points.length; i++) - { - points[i] += 0.5; - } - } - - // get first and last point.. figure out the middle! - var firstPoint = new math.Point(points[0], points[1]); - var lastPoint = new math.Point(points[points.length - 2], points[points.length - 1]); - - // if the first point is the last point - gonna have issues :) - if (firstPoint.x === lastPoint.x && firstPoint.y === lastPoint.y) - { - // need to clone as we are going to slightly modify the shape.. - points = points.slice(); - - points.pop(); - points.pop(); - - lastPoint = new math.Point(points[points.length - 2], points[points.length - 1]); - - var midPointX = lastPoint.x + (firstPoint.x - lastPoint.x) *0.5; - var midPointY = lastPoint.y + (firstPoint.y - lastPoint.y) *0.5; - - points.unshift(midPointX, midPointY); - points.push(midPointX, midPointY); - } - - var verts = webGLData.points; - var indices = webGLData.indices; - var length = points.length / 2; - var indexCount = points.length; - var indexStart = verts.length/6; - - // DRAW the Line - var width = graphicsData.lineWidth / 2; - - // sort color - var color = utils.hex2rgb(graphicsData.lineColor); - var alpha = graphicsData.lineAlpha; - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var px, py, p1x, p1y, p2x, p2y, p3x, p3y; - var perpx, perpy, perp2x, perp2y, perp3x, perp3y; - var a1, b1, c1, a2, b2, c2; - var denom, pdist, dist; - - p1x = points[0]; - p1y = points[1]; - - p2x = points[2]; - p2y = points[3]; - - perpx = -(p1y - p2y); - perpy = p1x - p2x; - - dist = Math.sqrt(perpx*perpx + perpy*perpy); - - perpx /= dist; - perpy /= dist; - perpx *= width; - perpy *= width; - - // start - verts.push(p1x - perpx , p1y - perpy, - r, g, b, alpha); - - verts.push(p1x + perpx , p1y + perpy, - r, g, b, alpha); - - for (i = 1; i < length-1; i++) - { - p1x = points[(i-1)*2]; - p1y = points[(i-1)*2 + 1]; - - p2x = points[(i)*2]; - p2y = points[(i)*2 + 1]; - - p3x = points[(i+1)*2]; - p3y = points[(i+1)*2 + 1]; - - perpx = -(p1y - p2y); - perpy = p1x - p2x; - - dist = Math.sqrt(perpx*perpx + perpy*perpy); - perpx /= dist; - perpy /= dist; - perpx *= width; - perpy *= width; - - perp2x = -(p2y - p3y); - perp2y = p2x - p3x; - - dist = Math.sqrt(perp2x*perp2x + perp2y*perp2y); - perp2x /= dist; - perp2y /= dist; - perp2x *= width; - perp2y *= width; - - a1 = (-perpy + p1y) - (-perpy + p2y); - b1 = (-perpx + p2x) - (-perpx + p1x); - c1 = (-perpx + p1x) * (-perpy + p2y) - (-perpx + p2x) * (-perpy + p1y); - a2 = (-perp2y + p3y) - (-perp2y + p2y); - b2 = (-perp2x + p2x) - (-perp2x + p3x); - c2 = (-perp2x + p3x) * (-perp2y + p2y) - (-perp2x + p2x) * (-perp2y + p3y); - - denom = a1*b2 - a2*b1; - - if (Math.abs(denom) < 0.1 ) - { - - denom+=10.1; - verts.push(p2x - perpx , p2y - perpy, - r, g, b, alpha); - - verts.push(p2x + perpx , p2y + perpy, - r, g, b, alpha); - - continue; - } - - px = (b1*c2 - b2*c1)/denom; - py = (a2*c1 - a1*c2)/denom; - - - pdist = (px -p2x) * (px -p2x) + (py -p2y) + (py -p2y); - - - if (pdist > 140 * 140) - { - perp3x = perpx - perp2x; - perp3y = perpy - perp2y; - - dist = Math.sqrt(perp3x*perp3x + perp3y*perp3y); - perp3x /= dist; - perp3y /= dist; - perp3x *= width; - perp3y *= width; - - verts.push(p2x - perp3x, p2y -perp3y); - verts.push(r, g, b, alpha); - - verts.push(p2x + perp3x, p2y +perp3y); - verts.push(r, g, b, alpha); - - verts.push(p2x - perp3x, p2y -perp3y); - verts.push(r, g, b, alpha); - - indexCount++; - } - else - { - - verts.push(px , py); - verts.push(r, g, b, alpha); - - verts.push(p2x - (px-p2x), p2y - (py - p2y)); - verts.push(r, g, b, alpha); - } - } - - p1x = points[(length-2)*2]; - p1y = points[(length-2)*2 + 1]; - - p2x = points[(length-1)*2]; - p2y = points[(length-1)*2 + 1]; - - perpx = -(p1y - p2y); - perpy = p1x - p2x; - - dist = Math.sqrt(perpx*perpx + perpy*perpy); - perpx /= dist; - perpy /= dist; - perpx *= width; - perpy *= width; - - verts.push(p2x - perpx , p2y - perpy); - verts.push(r, g, b, alpha); - - verts.push(p2x + perpx , p2y + perpy); - verts.push(r, g, b, alpha); - - indices.push(indexStart); - - for (i = 0; i < indexCount; i++) - { - indices.push(indexStart++); - } - - indices.push(indexStart-1); -}; - -/** - * Builds a complex polygon to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildComplexPoly = function (graphicsData, webGLData) -{ - //TODO - no need to copy this as it gets turned into a FLoat32Array anyways.. - var points = graphicsData.points.slice(); - - if (points.length < 6) - { - return; - } - - // get first and last point.. figure out the middle! - var indices = webGLData.indices; - webGLData.points = points; - webGLData.alpha = graphicsData.fillAlpha; - webGLData.color = utils.hex2rgb(graphicsData.fillColor); - - // calclate the bounds.. - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - var x,y; - - // get size.. - for (var i = 0; i < points.length; i+=2) - { - x = points[i]; - y = points[i+1]; - - minX = x < minX ? x : minX; - maxX = x > maxX ? x : maxX; - - minY = y < minY ? y : minY; - maxY = y > maxY ? y : maxY; - } - - // add a quad to the end cos there is no point making another buffer! - points.push(minX, minY, - maxX, minY, - maxX, maxY, - minX, maxY); - - // push a quad onto the end.. - - //TODO - this aint needed! - var length = points.length / 2; - for (i = 0; i < length; i++) - { - indices.push( i ); - } - -}; - -/** - * Builds a polygon to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildPoly = function (graphicsData, webGLData) -{ - var points = graphicsData.points; - - if (points.length < 6) - { - return; - } - - // get first and last point.. figure out the middle! - var verts = webGLData.points; - var indices = webGLData.indices; - - var length = points.length / 2; - - // sort color - var color = utils.hex2rgb(graphicsData.fillColor); - var alpha = graphicsData.fillAlpha; - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var triangles = utils.PolyK.Triangulate(points); - - if (!triangles) { - return false; - } - - var vertPos = verts.length / 6; - - var i = 0; - - for (i = 0; i < triangles.length; i+=3) - { - indices.push(triangles[i] + vertPos); - indices.push(triangles[i] + vertPos); - indices.push(triangles[i+1] + vertPos); - indices.push(triangles[i+2] +vertPos); - indices.push(triangles[i+2] + vertPos); - } - - for (i = 0; i < length; i++) - { - verts.push(points[i * 2], points[i * 2 + 1], - r, g, b, alpha); - } - - return true; -}; - -GraphicsRenderer.graphicsDataPool = []; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js new file mode 100644 index 0000000..2a48af4 --- /dev/null +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -0,0 +1,44 @@ +/** + * + * @class + * @private + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this object renderer works for. + */ +function ObjectRenderer(renderer) +{ + /** + * The renderer used by this object renderer. + * + * @member {WebGLRenderer} + */ + this.renderer = renderer; +} + +ObjectRenderer.prototype.constructor = ObjectRenderer; +module.exports = ObjectRenderer; + +ObjectRenderer.prototype.start = function () +{ + // set the shader.. +} + +ObjectRenderer.prototype.stop = function () +{ + // flush! +} + +ObjectRenderer.prototype.flush = function () +{ + // flush! +} + +ObjectRenderer.prototype.render = function (/* object */) +{ + // render the object +} + +ObjectRenderer.prototype.destroy = function () +{ + this.renderer = null; +} diff --git a/src/core/renderers/webgl/utils/SpriteRenderer.js b/src/core/renderers/webgl/utils/SpriteRenderer.js deleted file mode 100644 index dfb2018..0000000 --- a/src/core/renderers/webgl/utils/SpriteRenderer.js +++ /dev/null @@ -1,525 +0,0 @@ -var TextureUvs = require('../../../textures/TextureUvs'), - SpriteShader = require('../shaders/SpriteShader'); - -/** - * @author Mat Groves - * - * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ - * for creating the original pixi version! - * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer - * - * Heavily inspired by LibGDX's SpriteRenderer: - * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/SpriteRenderer.java - */ - -/** - * - * @class - * @private - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this sprite batch works for. - */ -function SpriteRenderer(renderer) -{ - /** - * - * - * @member {WebGLRenderer} - */ - this.renderer = renderer; - - /** - * - * - * @member {number} - */ - this.vertSize = 5; - - /** - * - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = 2000;//Math.pow(2, 16) / this.vertSize; - - // the total number of bytes in our batch - var numVerts = this.size * 4 * this.vertByteSize; - // the total number of indices in our batch - var numIndices = this.size * 6; - - /** - * Holds the vertices - * - * @member {ArrayBuffer} - */ - this.vertices = new ArrayBuffer(numVerts); - - /** - * View on the vertices as a Float32Array - * - * @member {Float32Array} - */ - this.positions = new Float32Array(this.vertices); - - /** - * View on the vertices as a Uint32Array - * - * @member {Uint32Array} - */ - this.colors = new Uint32Array(this.vertices); - - /** - * Holds the indices - * - * @member {Uint16Array} - */ - this.indices = new Uint16Array(numIndices); - - /** - * - * - * @member {number} - */ - this.lastIndexCount = 0; - - for (var i=0, j=0; i < numIndices; i += 6, j += 4) - { - this.indices[i + 0] = j + 0; - this.indices[i + 1] = j + 1; - this.indices[i + 2] = j + 2; - this.indices[i + 3] = j + 0; - this.indices[i + 4] = j + 2; - this.indices[i + 5] = j + 3; - } - - /** - * - * - * @member {boolean} - */ - this.drawing = false; - - /** - * - * - * @member {number} - */ - this.currentBatchSize = 0; - - /** - * - * - * @member {BaseTexture} - */ - this.currentBaseTexture = null; - - /** - * - * - * @member {boolean} - */ - this.dirty = true; - - /** - * - * - * @member {Array} - */ - this.textures = []; - - /** - * - * - * @member {Array} - */ - this.blendModes = []; - - /** - * - * - * @member {Array} - */ - this.shaders = []; - - /** - * - * - * @member {Array} - */ - this.sprites = []; - - /** - * The default shader that is used if a sprite doesn't have a more specific one. - * - * @member {Shader} - */ - this.shader = null; - - // listen for context and update necessary buffers - var self = this; - this.renderer.on('context', function () - { - self.setupContext(); - }); -} - -SpriteRenderer.prototype.constructor = SpriteRenderer; -module.exports = SpriteRenderer; - -/** - * @param gl {WebGLContext} the current WebGL drawing context - */ -SpriteRenderer.prototype.setupContext = function () -{ - var gl = this.renderer.gl; - - // setup default shader - this.shader = new SpriteShader(gl); - - // create a couple of buffers - this.vertexBuffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - - // 65535 is max index, so 65535 / 6 = 10922. - - //upload the index data - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); - - this.currentBlendMode = 99999; -}; - - -/** - * @param sprite {Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite.texture; - - //TODO set blend modes.. - // check texture.. - if (this.currentBatchSize >= this.size) - { - this.flush(); - this.currentBaseTexture = texture.baseTexture; - } - - // get the uvs for the texture - var uvs = texture._uvs; - - // if the uvs have not updated then no point rendering just yet! - if (!uvs) - { - return; - } - - // TODO trim?? - var aX = sprite.anchor.x; - var aY = sprite.anchor.y; - - var w0, w1, h0, h1; - - if (texture.trim) - { - // if the sprite is trimmed then we need to add the extra space before transforming the sprite coords.. - var trim = texture.trim; - - w1 = trim.x - aX * trim.width; - w0 = w1 + texture.crop.width; - - h1 = trim.y - aY * trim.height; - h0 = h1 + texture.crop.height; - - } - else - { - w0 = (texture.frame.width ) * (1-aX); - w1 = (texture.frame.width ) * -aX; - - h0 = texture.frame.height * (1-aY); - h1 = texture.frame.height * -aY; - } - - var index = this.currentBatchSize * this.vertByteSize; - - var resolution = texture.baseTexture.resolution; - - var worldTransform = sprite.worldTransform; - - var a = worldTransform.a / resolution; - var b = worldTransform.b / resolution; - var c = worldTransform.c / resolution; - var d = worldTransform.d / resolution; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var colors = this.colors; - var positions = this.positions; - - if (this.renderer.roundPixels) - { - // xy - positions[index] = a * w1 + c * h1 + tx | 0; - positions[index+1] = d * h1 + b * w1 + ty | 0; - - // xy - positions[index+5] = a * w0 + c * h1 + tx | 0; - positions[index+6] = d * h1 + b * w0 + ty | 0; - - // xy - positions[index+10] = a * w0 + c * h0 + tx | 0; - positions[index+11] = d * h0 + b * w0 + ty | 0; - - // xy - positions[index+15] = a * w1 + c * h0 + tx | 0; - positions[index+16] = d * h0 + b * w1 + ty | 0; - } - else - { - // xy - positions[index] = a * w1 + c * h1 + tx; - positions[index+1] = d * h1 + b * w1 + ty; - - // xy - positions[index+5] = a * w0 + c * h1 + tx; - positions[index+6] = d * h1 + b * w0 + ty; - - // xy - positions[index+10] = a * w0 + c * h0 + tx; - positions[index+11] = d * h0 + b * w0 + ty; - - // xy - positions[index+15] = a * w1 + c * h0 + tx; - positions[index+16] = d * h0 + b * w1 + ty; - } - - // uv - positions[index+2] = uvs.x0; - positions[index+3] = uvs.y0; - - // uv - positions[index+7] = uvs.x1; - positions[index+8] = uvs.y1; - - // uv - positions[index+12] = uvs.x2; - positions[index+13] = uvs.y2; - - // uv - positions[index+17] = uvs.x3; - positions[index+18] = uvs.y3; - - // color and alpha - var tint = sprite.tint; - colors[index+4] = colors[index+9] = colors[index+14] = colors[index+19] = (tint >> 16) + (tint & 0xff00) + ((tint & 0xff) << 16) + (sprite.worldAlpha * 255 << 24); - - // increment the batchsize - this.sprites[this.currentBatchSize++] = sprite; - - -}; - - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - // If the batch is length 0 then return as there is nothing to draw - if (this.currentBatchSize === 0) - { - return; - } - - var gl = this.renderer.gl; - var shader; - - if (this.dirty) - { - this.dirty = false; - // bind the main texture - gl.activeTexture(gl.TEXTURE0); - - // bind the buffers - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - - // this is the same for each shader? - var stride = this.vertByteSize; - gl.vertexAttribPointer(this.shader.attributes.aVertexPosition, 2, gl.FLOAT, false, stride, 0); - gl.vertexAttribPointer(this.shader.attributes.aTextureCoord, 2, gl.FLOAT, false, stride, 2 * 4); - - // color attributes will be interpreted as unsigned bytes and normalized - gl.vertexAttribPointer(this.shader.attributes.aColor, 4, gl.UNSIGNED_BYTE, true, stride, 4 * 4); - } - - // upload the verts to the buffer - if (this.currentBatchSize > ( this.size * 0.5 ) ) - { - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertices); - } - else - { - var view = this.positions.subarray(0, this.currentBatchSize * this.vertByteSize); - gl.bufferSubData(gl.ARRAY_BUFFER, 0, view); - } - - var nextTexture, nextBlendMode, nextShader; - var batchSize = 0; - var start = 0; - - var currentBaseTexture = null; - var currentBlendMode = this.renderer.blendModeManager.currentBlendMode; - var currentShader = null; - - var blendSwap = false; - var shaderSwap = false; - var sprite; - - for (var i = 0, j = this.currentBatchSize; i < j; i++) - { - - sprite = this.sprites[i]; - - nextTexture = sprite.texture.baseTexture; - nextBlendMode = sprite.blendMode; - nextShader = sprite.shader || this.shader; - - blendSwap = currentBlendMode !== nextBlendMode; - shaderSwap = currentShader !== nextShader; // should I use uuidS??? - - if (currentBaseTexture !== nextTexture || blendSwap || shaderSwap) - { - this.renderBatch(currentBaseTexture, batchSize, start); - - start = i; - batchSize = 0; - currentBaseTexture = nextTexture; - - if (blendSwap) - { - currentBlendMode = nextBlendMode; - this.renderer.blendModeManager.setBlendMode( currentBlendMode ); - } - - if (shaderSwap) - { - currentShader = nextShader; - - shader = currentShader.shaders ? currentShader.shaders[gl.id] : currentShader; - - if (!shader) - { - shader = new Shader(gl, null, currentShader.fragmentSrc, currentShader.uniforms); - currentShader.shaders[gl.id] = shader; - } - - // set shader function??? - this.renderer.shaderManager.setShader(shader); - - if (shader.dirty) - { - shader.syncUniforms(); - } - - // both thease only need to be set if they are changing.. - // set the projection - var projection = this.renderer.projection; - gl.uniform2f(shader.uniforms.projectionVector._location, projection.x, projection.y); - - // TODO - this is temprorary! - var offsetVector = this.renderer.offset; - gl.uniform2f(shader.uniforms.offsetVector._location, offsetVector.x, offsetVector.y); - - // set the pointers - } - } - - batchSize++; - } - - this.renderBatch(currentBaseTexture, batchSize, start); - - // then reset the batch! - this.currentBatchSize = 0; -}; - -/** - * @param texture {Texture} - * @param size {number} - * @param startIndex {number} - */ -SpriteRenderer.prototype.renderBatch = function (texture, size, startIndex) -{ - if (size === 0) - { - return; - } - - var gl = this.renderer.gl; - - if (!texture._glTextures[gl.id]) - { - this.renderer.updateTexture(texture); - } - else - { - // bind the current texture - gl.bindTexture(gl.TEXTURE_2D, texture._glTextures[gl.id]); - } - - // now draw those suckas! - gl.drawElements(gl.TRIANGLES, size * 6, gl.UNSIGNED_SHORT, startIndex * 6 * 2); - - // increment the draw count - this.renderer.drawCount++; -}; - -/** - * - */ -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.dirty = true; -}; - -/** - * - */ -SpriteRenderer.prototype.start = function () -{ - this.dirty = true; -}; - -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - this.renderer.gl.deleteBuffer(this.vertexBuffer); - this.renderer.gl.deleteBuffer(this.indexBuffer); - - this.vertices = null; - this.indices = null; - - this.vertexBuffer = null; - this.indexBuffer = null; - - this.currentBaseTexture = null; - - this.renderer = null; -}; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js new file mode 100644 index 0000000..8e3710a --- /dev/null +++ b/src/core/sprites/Sprite.js @@ -0,0 +1,458 @@ +var math = require('../math'), + Texture = require('../textures/Texture'), + DisplayObjectContainer = require('./DisplayObjectContainer'), + CanvasTinter = require('../renderers/canvas/utils/CanvasTinter'), + utils = require('../utils'), + CONST = require('../const'); + +/** + * The Sprite object is the base for all textured objects that are rendered to the screen + * + * A sprite can be created directly from an image like this: + * + * ```js + * var sprite = new Sprite.fromImage('assets/image.png'); + * ``` + * + * @class Sprite + * @extends DisplayObjectContainer + * @namespace PIXI + * @param texture {Texture} The texture for this sprite + */ +function Sprite(texture) +{ + DisplayObjectContainer.call(this); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting than anchor to 0.5,0.5 means the textures origin is centered + * Setting the anchor to 1,1 would mean the textures origin points will be the bottom right corner + * + * @member {Point} + */ + this.anchor = new math.Point(); + + /** + * The texture that the sprite is using + * + * @member {Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Set to CONST.blendModes.NORMAL to remove any blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {AbstractFilter} + */ + this.shader = null; + + this.renderable = true; + + // call texture setter + this.texture = texture || Texture.EMPTY; +} + +Sprite.prototype.destroy = function (destroyTexture, destroyBaseTexture) +{ + DisplayObjectContainer.prototype.destroy.call(this); + + this.anchor = null; + + if (destroyTexture) + { + this._texture.destroy(destroyBaseTexture); + } + + this._texture = null; + this.shader = null; +}; + +// constructor +Sprite.prototype = Object.create(DisplayObjectContainer.prototype); +Sprite.prototype.constructor = Sprite; +module.exports = Sprite; + +Object.defineProperties(Sprite.prototype, { + /** + * The width of the sprite, setting this will actually modify the scale to achieve the value set + * + * @member + * @memberof Sprite# + */ + width: { + get: function () + { + return this.scale.x * this.texture.frame.width; + }, + set: function (value) + { + this.scale.x = value / this.texture.frame.width; + this._width = value; + } + }, + + /** + * The height of the sprite, setting this will actually modify the scale to achieve the value set + * + * @member + * @memberof Sprite# + */ + height: { + get: function () + { + return this.scale.y * this.texture.frame.height; + }, + set: function (value) + { + this.scale.y = value / this.texture.frame.height; + this._height = value; + } + }, + + /** + * The height of the sprite, setting this will actually modify the scale to achieve the value set + * + * @member + * @memberof Sprite# + */ + texture: { + get: function () + { + return this._texture; + }, + set: function (value) + { + if (this._texture === value) + { + return; + } + + this._texture = value; + this.cachedTint = 0xFFFFFF; + + if (value) + { + // wait for the texture to load + if (value.baseTexture.hasLoaded) + { + this._onTextureUpdate(); + } + else + { + value.once('update', this._onTextureUpdate.bind(this)); + } + } + } + }, +}); + +/** + * When the texture is updated, this event will fire to update the scale and frame + * + * @private + */ +Sprite.prototype._onTextureUpdate = function () +{ + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = this._width / this.texture.frame.width; + } + + if (this._height) + { + this.scale.y = this._height / this.texture.frame.height; + } +}; + +Sprite.prototype._renderWebGL = function (renderer) +{ + renderer.setObjectRenderer(renderer.objectRenderer.sprite); + renderer.objectRenderer.sprite.render(this); +}; + +/** + * Returns the bounds of the Sprite as a rectangle. The bounds calculation takes the worldTransform into account. + * + * @param matrix {Matrix} the transformation matrix of the sprite + * @return {Rectangle} the framing rectangle + */ +Sprite.prototype.getBounds = function (matrix) +{ + var width = this.texture.frame.width; + var height = this.texture.frame.height; + + var w0 = width * (1-this.anchor.x); + var w1 = width * -this.anchor.x; + + var h0 = height * (1-this.anchor.y); + var h1 = height * -this.anchor.y; + + var worldTransform = matrix || this.worldTransform ; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var minX, + maxX, + minY, + maxY; + + if(b === 0 && c === 0) + { + // scale may be negative! + if (a < 0) + { + a *= -1; + } + + if (d < 0) + { + d *= -1; + } + + // this means there is no rotation going on right? RIGHT? + // if thats the case then we can avoid checking the bound values! yay + minX = a * w1 + tx; + maxX = a * w0 + tx; + minY = d * h1 + ty; + maxY = d * h0 + ty; + } + else + { + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + minX = x1; + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y1; + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x1; + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y1; + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + } + + var bounds = this._bounds; + + bounds.x = minX; + bounds.width = maxX - minX; + + bounds.y = minY; + bounds.height = maxY - minY; + + // store a reference so that if this function gets called again in the render cycle we do not have to recalculate + this._currentBounds = bounds; + + return bounds; +}; + +/** +* Renders the object using the Canvas renderer +* +* @param renderer {CanvasRenderer} The renderer +*/ +Sprite.prototype.renderCanvas = function (renderer) +{ + if (!this.visible || this.alpha <= 0 || this.texture.crop.width <= 0 || this.texture.crop.height <= 0) + { + return; + } + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + renderer.context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + // Ignore null sources + if (this.texture.valid) + { + var resolution = this.texture.baseTexture.resolution / renderer.resolution; + + renderer.context.globalAlpha = this.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for this texture + if (renderer.smoothProperty && renderer.scaleMode !== this.texture.baseTexture.scaleMode) + { + renderer.scaleMode = this.texture.baseTexture.scaleMode; + renderer.context[renderer.smoothProperty] = (renderer.scaleMode === CONST.scaleModes.LINEAR); + } + + // If the texture is trimmed we offset by the trim x/y, otherwise we use the frame dimensions + var dx = (this.texture.trim ? this.texture.trim.x : 0) - (this.anchor.x * this.texture.trim.width); + var dy = (this.texture.trim ? this.texture.trim.y : 0) - (this.anchor.y * this.texture.trim.height); + + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + this.worldTransform.a, + this.worldTransform.b, + this.worldTransform.c, + this.worldTransform.d, + (this.worldTransform.tx * renderer.resolution) | 0, + (this.worldTransform.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + this.worldTransform.a, + this.worldTransform.b, + this.worldTransform.c, + this.worldTransform.d, + this.worldTransform.tx * renderer.resolution, + this.worldTransform.ty * renderer.resolution + ); + } + + if (this.tint !== 0xFFFFFF) + { + if (this.cachedTint !== this.tint) + { + this.cachedTint = this.tint; + + // TODO clean up caching - how to clean up the caches? + this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); + } + + renderer.context.drawImage( + this.tintedTexture, + 0, + 0, + this.texture.crop.width, + this.texture.crop.height, + dx / resolution, + dy / resolution, + this.texture.crop.width / resolution, + this.texture.crop.height / resolution + ); + } + else + { + renderer.context.drawImage( + this.texture.baseTexture.source, + this.texture.crop.x, + this.texture.crop.y, + this.texture.crop.width, + this.texture.crop.height, + dx / resolution, + dy / resolution, + this.texture.crop.width / resolution, + this.texture.crop.height / resolution + ); + } + } + + for (var i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } +}; + +// some helper functions.. + +/** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {String} The frame Id of the texture in the cache + * @return {Sprite} A new Sprite using a texture from the texture cache matching the frameId + */ +Sprite.fromFrame = function (frameId) +{ + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache' + this); + } + + return new Sprite(texture); +}; + +/** + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded + * + * @static + * @param imageId {String} The image url of the texture + * @return {Sprite} A new Sprite using a texture from the texture cache matching the image id + */ +Sprite.fromImage = function (imageId, crossorigin, scaleMode) +{ + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); +}; diff --git a/src/core/sprites/SpriteBatch.js b/src/core/sprites/SpriteBatch.js new file mode 100644 index 0000000..c196fef --- /dev/null +++ b/src/core/sprites/SpriteBatch.js @@ -0,0 +1,183 @@ +var DisplayObjectContainer = require('./DisplayObjectContainer'), + WebGLFastSpriteBatch = require('../renderers/webgl/utils/WebGLFastSpriteBatch'); + +/** + * The SpriteBatch class is a really fast version of the DisplayObjectContainer built solely for speed, + * so use when you need a lot of sprites or particles. The tradeoff of the SpriteBatch is that advanced + * functionality will not work. SpriteBatch implements only the basic object transform (position, scale, rotation). + * Any other functionality like tinting, masking, etc will not work on sprites in this batch. + * + * It's extremely easy to use : + * + * ```js + * var container = new SpriteBatch(); + * + * for(var i = 0; i < 100; ++i) + * { + * var sprite = new PIXI.Sprite.fromImage("myImage.png"); + * container.addChild(sprite); + * } + * ``` + * + * And here you have a hundred sprites that will be renderer at the speed of light. + * + * @class + * @namespace PIXI + */ + +//TODO RENAME to PARTICLE CONTAINER? +function SpriteBatch() +{ + DisplayObjectContainer.call(this); +} + +SpriteBatch.prototype = Object.create(DisplayObjectContainer.prototype); +SpriteBatch.prototype.constructor = SpriteBatch; +module.exports = SpriteBatch; + +/** + * Updates the object transform for rendering + * + * @private + */ +SpriteBatch.prototype.updateTransform = function () +{ + // TODO don't need to! + this.displayObjectUpdateTransform(); + // PIXI.DisplayObjectContainer.prototype.updateTransform.call( this ); +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} The webgl renderer + * @private + */ +SpriteBatch.prototype.renderWebGL = function (renderer) +{ + if (!this.visible || this.alpha <= 0 || !this.children.length) + { + return; + } + + renderer.spriteBatch.stop(); + + renderer.shaderManager.setShader(renderer.shaderManager.fastShader); + + renderer.fastSpriteBatch.begin(this); + renderer.fastSpriteBatch.render(this); + + renderer.spriteBatch.start(); +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} The canvas renderer + * @private + */ +SpriteBatch.prototype.renderCanvas = function (renderer) +{ + if (!this.visible || this.alpha <= 0 || !this.children.length) + { + return; + } + + var context = renderer.context; + var transform = this.worldTransform; + var isRotated = true; + + context.globalAlpha = this.worldAlpha; + + this.displayObjectUpdateTransform(); + + for (var i = 0; i < this.children.length; ++i) + { + var child = this.children[i]; + + if (!child.visible) + { + continue; + } + + var frame = child.texture.frame; + + context.globalAlpha = this.worldAlpha * child.alpha; + + if (child.rotation % (Math.PI * 2) === 0) + { + // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call + if (isRotated) + { + context.setTransform( + transform.a, + transform.b, + transform.c, + transform.d, + transform.tx, + transform.ty + ); + + isRotated = false; + } + + context.drawImage( + child.texture.baseTexture.source, + frame.x, + frame.y, + frame.width, + frame.height, + ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5) | 0, + ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5) | 0, + frame.width * child.scale.x, + frame.height * child.scale.y + ); + } + else + { + if (!isRotated) + { + isRotated = true; + } + + child.displayObjectUpdateTransform(); + + var childTransform = child.worldTransform; + + if (renderer.roundPixels) + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx | 0, + childTransform.ty | 0 + ); + } + else + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx, + childTransform.ty + ); + } + + context.drawImage( + child.texture.baseTexture.source, + frame.x, + frame.y, + frame.width, + frame.height, + ((child.anchor.x) * (-frame.width) + 0.5) | 0, + ((child.anchor.y) * (-frame.height) + 0.5) | 0, + frame.width, + frame.height + ); + } + } +}; diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 0669e79..c2294f9 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,7 +500,7 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.spriteRenderer.flush(); + renderer.objectRenderers.sprite.flush(); renderer.filterManager.pushFilter(this._filterBlock); } diff --git a/src/core/display/Sprite.js b/src/core/display/Sprite.js deleted file mode 100644 index e4e9d01..0000000 --- a/src/core/display/Sprite.js +++ /dev/null @@ -1,461 +0,0 @@ -var math = require('../math'), - Texture = require('../textures/Texture'), - DisplayObjectContainer = require('./DisplayObjectContainer'), - CanvasTinter = require('../renderers/canvas/utils/CanvasTinter'), - utils = require('../utils'), - CONST = require('../const'); - -/** - * The Sprite object is the base for all textured objects that are rendered to the screen - * - * A sprite can be created directly from an image like this: - * - * ```js - * var sprite = new Sprite.fromImage('assets/image.png'); - * ``` - * - * @class Sprite - * @extends DisplayObjectContainer - * @namespace PIXI - * @param texture {Texture} The texture for this sprite - */ -function Sprite(texture) -{ - DisplayObjectContainer.call(this); - - /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting than anchor to 0.5,0.5 means the textures origin is centered - * Setting the anchor to 1,1 would mean the textures origin points will be the bottom right corner - * - * @member {Point} - */ - this.anchor = new math.Point(); - - /** - * The texture that the sprite is using - * - * @member {Texture} - * @private - */ - this._texture = null; - - /** - * The width of the sprite (this is initially set by the texture) - * - * @member {number} - * @private - */ - this._width = 0; - - /** - * The height of the sprite (this is initially set by the texture) - * - * @member {number} - * @private - */ - this._height = 0; - - /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the sprite. Set to CONST.blendModes.NORMAL to remove any blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. - * - * @member {AbstractFilter} - */ - this.shader = null; - - this.renderable = true; - - // call texture setter - this.texture = texture || Texture.EMPTY; -} - -Sprite.prototype.destroy = function (destroyTexture, destroyBaseTexture) -{ - DisplayObjectContainer.prototype.destroy.call(this); - - this.anchor = null; - - if (destroyTexture) - { - this._texture.destroy(destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// constructor -Sprite.prototype = Object.create(DisplayObjectContainer.prototype); -Sprite.prototype.constructor = Sprite; -module.exports = Sprite; - -Object.defineProperties(Sprite.prototype, { - /** - * The width of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - width: { - get: function () - { - return this.scale.x * this.texture.frame.width; - }, - set: function (value) - { - this.scale.x = value / this.texture.frame.width; - this._width = value; - } - }, - - /** - * The height of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - height: { - get: function () - { - return this.scale.y * this.texture.frame.height; - }, - set: function (value) - { - this.scale.y = value / this.texture.frame.height; - this._height = value; - } - }, - - /** - * The height of the sprite, setting this will actually modify the scale to achieve the value set - * - * @member - * @memberof Sprite# - */ - texture: { - get: function () - { - return this._texture; - }, - set: function (value) - { - if (this._texture === value) - { - return; - } - - this._texture = value; - this.cachedTint = 0xFFFFFF; - - if (value) - { - // wait for the texture to load - if (value.baseTexture.hasLoaded) - { - this._onTextureUpdate(); - } - else - { - value.once('update', this._onTextureUpdate.bind(this)); - } - } - } - }, -}); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = this._width / this.texture.frame.width; - } - - if (this._height) - { - this.scale.y = this._height / this.texture.frame.height; - } -}; - -Sprite.prototype._renderWebGL = function (renderer) -{ - - // this is where content gets renderd.. - // watch this space for a little render state manager.. - renderer.setObjectRenderer(renderer.spriteRenderer); - renderer.spriteRenderer.render(this); -}; - -/** - * Returns the bounds of the Sprite as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param matrix {Matrix} the transformation matrix of the sprite - * @return {Rectangle} the framing rectangle - */ -Sprite.prototype.getBounds = function (matrix) -{ - var width = this.texture.frame.width; - var height = this.texture.frame.height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = matrix || this.worldTransform ; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var minX, - maxX, - minY, - maxY; - - if(b === 0 && c === 0) - { - // scale may be negative! - if (a < 0) - { - a *= -1; - } - - if (d < 0) - { - d *= -1; - } - - // this means there is no rotation going on right? RIGHT? - // if thats the case then we can avoid checking the bound values! yay - minX = a * w1 + tx; - maxX = a * w0 + tx; - minY = d * h1 + ty; - maxY = d * h0 + ty; - } - else - { - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - } - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {CanvasRenderer} The renderer -*/ -Sprite.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || this.texture.crop.width <= 0 || this.texture.crop.height <= 0) - { - return; - } - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - renderer.context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - // Ignore null sources - if (this.texture.valid) - { - var resolution = this.texture.baseTexture.resolution / renderer.resolution; - - renderer.context.globalAlpha = this.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for this texture - if (renderer.smoothProperty && renderer.scaleMode !== this.texture.baseTexture.scaleMode) - { - renderer.scaleMode = this.texture.baseTexture.scaleMode; - renderer.context[renderer.smoothProperty] = (renderer.scaleMode === CONST.scaleModes.LINEAR); - } - - // If the texture is trimmed we offset by the trim x/y, otherwise we use the frame dimensions - var dx = (this.texture.trim ? this.texture.trim.x : 0) - (this.anchor.x * this.texture.trim.width); - var dy = (this.texture.trim ? this.texture.trim.y : 0) - (this.anchor.y * this.texture.trim.height); - - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - this.worldTransform.a, - this.worldTransform.b, - this.worldTransform.c, - this.worldTransform.d, - (this.worldTransform.tx * renderer.resolution) | 0, - (this.worldTransform.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - this.worldTransform.a, - this.worldTransform.b, - this.worldTransform.c, - this.worldTransform.d, - this.worldTransform.tx * renderer.resolution, - this.worldTransform.ty * renderer.resolution - ); - } - - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - // TODO clean up caching - how to clean up the caches? - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - - renderer.context.drawImage( - this.tintedTexture, - 0, - 0, - this.texture.crop.width, - this.texture.crop.height, - dx / resolution, - dy / resolution, - this.texture.crop.width / resolution, - this.texture.crop.height / resolution - ); - } - else - { - renderer.context.drawImage( - this.texture.baseTexture.source, - this.texture.crop.x, - this.texture.crop.y, - this.texture.crop.width, - this.texture.crop.height, - dx / resolution, - dy / resolution, - this.texture.crop.width / resolution, - this.texture.crop.height / resolution - ); - } - } - - for (var i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -// some helper functions.. - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {String} The frame Id of the texture in the cache - * @return {Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache' + this); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {String} The image url of the texture - * @return {Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/display/SpriteBatch.js b/src/core/display/SpriteBatch.js deleted file mode 100644 index c196fef..0000000 --- a/src/core/display/SpriteBatch.js +++ /dev/null @@ -1,183 +0,0 @@ -var DisplayObjectContainer = require('./DisplayObjectContainer'), - WebGLFastSpriteBatch = require('../renderers/webgl/utils/WebGLFastSpriteBatch'); - -/** - * The SpriteBatch class is a really fast version of the DisplayObjectContainer built solely for speed, - * so use when you need a lot of sprites or particles. The tradeoff of the SpriteBatch is that advanced - * functionality will not work. SpriteBatch implements only the basic object transform (position, scale, rotation). - * Any other functionality like tinting, masking, etc will not work on sprites in this batch. - * - * It's extremely easy to use : - * - * ```js - * var container = new SpriteBatch(); - * - * for(var i = 0; i < 100; ++i) - * { - * var sprite = new PIXI.Sprite.fromImage("myImage.png"); - * container.addChild(sprite); - * } - * ``` - * - * And here you have a hundred sprites that will be renderer at the speed of light. - * - * @class - * @namespace PIXI - */ - -//TODO RENAME to PARTICLE CONTAINER? -function SpriteBatch() -{ - DisplayObjectContainer.call(this); -} - -SpriteBatch.prototype = Object.create(DisplayObjectContainer.prototype); -SpriteBatch.prototype.constructor = SpriteBatch; -module.exports = SpriteBatch; - -/** - * Updates the object transform for rendering - * - * @private - */ -SpriteBatch.prototype.updateTransform = function () -{ - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.DisplayObjectContainer.prototype.updateTransform.call( this ); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} The webgl renderer - * @private - */ -SpriteBatch.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || !this.children.length) - { - return; - } - - renderer.spriteBatch.stop(); - - renderer.shaderManager.setShader(renderer.shaderManager.fastShader); - - renderer.fastSpriteBatch.begin(this); - renderer.fastSpriteBatch.render(this); - - renderer.spriteBatch.start(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} The canvas renderer - * @private - */ -SpriteBatch.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.alpha <= 0 || !this.children.length) - { - return; - } - - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) - { - continue; - } - - var frame = child.texture.frame; - - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) - { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) - { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx, - transform.ty - ); - - isRotated = false; - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5) | 0, - ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5) | 0, - frame.width * child.scale.x, - frame.height * child.scale.y - ); - } - else - { - if (!isRotated) - { - isRotated = true; - } - - child.displayObjectUpdateTransform(); - - var childTransform = child.worldTransform; - - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx | 0, - childTransform.ty | 0 - ); - } - else - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx, - childTransform.ty - ); - } - - context.drawImage( - child.texture.baseTexture.source, - frame.x, - frame.y, - frame.width, - frame.height, - ((child.anchor.x) * (-frame.width) + 0.5) | 0, - ((child.anchor.y) * (-frame.height) + 0.5) | 0, - frame.width, - frame.height - ); - } - } -}; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js index 70d28b1..baab0f8 100644 --- a/src/core/primitives/Graphics.js +++ b/src/core/primitives/Graphics.js @@ -738,17 +738,17 @@ return; } - + */ - + if (this.glDirty) { this.dirty = true; this.glDirty = false; } - renderer.setObjectRenderer(renderer.graphicsRenderer); - renderer.graphicsRenderer.render(this); + renderer.setObjectRenderer(renderer.objectRenderers.graphics); + renderer.objectRenderers.graphics.render(this); }; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js new file mode 100644 index 0000000..55dc7a5 --- /dev/null +++ b/src/core/primitives/GraphicsRenderer.js @@ -0,0 +1,874 @@ +var utils = require('../../../utils'), + math = require('../../../math'), + CONST = require('../../../const'), + ObjectRenderer = require('../renderers/webgl/utils/ObjectRenderer'), + WebGLRenderer = require('../renderers/webgl/WebGLRenderer'), + WebGLGraphicsData = require('./WebGLGraphicsData'); + +/** + * Renders the graphics object. + * + * @class + * @private + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this object renderer works for. + */ +function GraphicsRenderer(renderer) +{ + ObjectRenderer.call(this, renderer); + + this.graphicsDataPool = []; +} + +GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); +GraphicsRenderer.prototype.constructor = GraphicsRenderer; +module.exports = GraphicsRenderer; + +WebGLRenderer.registerObjectRenderer('graphics', GraphicsRenderer); + +/** + * Destroys this renderer. + * + */ +GraphicsRenderer.prototype.destroy = function () { + ObjectRenderer.prototype.destroy.call(this); + + this.graphicsDataPool = null; +}; + +/** + * Renders a graphics object. + * + * @param graphics {Graphics} The graphics object to render. + */ +GraphicsRenderer.prototype.render = function(graphics) +{ + var renderer = this.renderer; + var gl = renderer.gl; + + var projection = renderer.projection, + offset = renderer.offset, + shader = renderer.shaderManager.primitiveShader, + webGLData; + + if (graphics.dirty) + { + this.updateGraphics(graphics, gl); + } + + var webGL = graphics._webGL[gl.id]; + + // This could be speeded up for sure! + + renderer.blendModeManager.setBlendMode( graphics.blendMode ); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.stencilManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.stencilManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.primitiveShader; + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniform1f(shader.uniforms.flipY._location, 1); + + gl.uniform2f(shader.uniforms.projectionVector._location, projection.x, -projection.y); + gl.uniform2f(shader.uniforms.offsetVector._location, -offset.x, -offset.y); + + gl.uniform3fv(shader.uniforms.tint._location, utils.hex2rgb(graphics.tint)); + + gl.uniform1f(shader.uniforms.alpha._location, graphics.worldAlpha); + + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.attributes.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); + gl.vertexAttribPointer(shader.attributes.aColor, 4, gl.FLOAT, false,4 * 6, 2 * 4); + + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + } + } +} + +/** + * Updates the graphics object + * + * @private + * @param graphicsData {Graphics} The graphics object to update + * @param gl {WebGLContext} the current WebGL drawing context + */ +GraphicsRenderer.prototype.updateGraphics = function(graphics) +{ + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[gl.id]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[gl.id] = {lastIndex:0, data:[], gl:gl}; + } + + // flag the graphics as not dirty as we are about to update it... + graphics.dirty = false; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty) + { + graphics.clearDirty = false; + + // lop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + graphicsData.reset(); + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + if (data.type === CONST.SHAPES.POLY) + { + // need to add the points the the graphics object.. + data.points = data.shape.points.slice(); + if (data.shape.closed) + { + // close the poly if the value is true! + if (data.points[0] !== data.points[data.points.length-2] || data.points[1] !== data.points[data.points.length-1]) + { + data.points.push(data.points[0], data.points[1]); + } + } + + // MAKE SURE WE HAVE THE CORRECT TYPE.. + if (data.fill) + { + if (data.points.length >= 6) + { + if (data.points.length < 6 * 2) + { + webGLData = this.switchMode(webGL, 0); + + var canDrawUsingSimple = this.buildPoly(data, webGLData); + // console.log(canDrawUsingSimple); + + if (!canDrawUsingSimple) + { + // console.log("<>>>") + webGLData = this.switchMode(webGL, 1); + this.buildComplexPoly(data, webGLData); + } + + } + else + { + webGLData = this.switchMode(webGL, 1); + this.buildComplexPoly(data, webGLData); + } + } + } + + if (data.lineWidth > 0) + { + webGLData = this.switchMode(webGL, 0); + this.buildLine(data, webGLData); + } + } + else + { + webGLData = this.switchMode(webGL, 0); + + if (data.type === CONST.SHAPES.RECT) + { + this.buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + this.buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + this.buildRoundedRectangle(data, webGLData); + } + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } +} + +/** + * + * + * @private + * @param webGL {WebGLContext} + * @param type {number} + */ +GraphicsRenderer.prototype.switchMode = function (webGL, type) +{ + var webGLData; + + if (!webGL.data.length) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(webGL.gl); + webGLData.mode = type; + webGL.data.push(webGLData); + } + else + { + webGLData = webGL.data[webGL.data.length-1]; + + if (webGLData.mode !== type || type === 1) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(webGL.gl); + webGLData.mode = type; + webGL.data.push(webGLData); + } + } + + webGLData.dirty = true; + + return webGLData; +}; + +/** + * Builds a rectangle to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildRectangle = function (graphicsData, webGLData) +{ + // --- // + // need to convert points to a nice regular data + // + var rectData = graphicsData.shape; + var x = rectData.x; + var y = rectData.y; + var width = rectData.width; + var height = rectData.height; + + if (graphicsData.fill) + { + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var verts = webGLData.points; + var indices = webGLData.indices; + + var vertPos = verts.length/6; + + // start + verts.push(x, y); + verts.push(r, g, b, alpha); + + verts.push(x + width, y); + verts.push(r, g, b, alpha); + + verts.push(x , y + height); + verts.push(r, g, b, alpha); + + verts.push(x + width, y + height); + verts.push(r, g, b, alpha); + + // insert 2 dead triangles.. + indices.push(vertPos, vertPos, vertPos+1, vertPos+2, vertPos+3, vertPos+3); + } + + if (graphicsData.lineWidth) + { + var tempPoints = graphicsData.points; + + graphicsData.points = [x, y, + x + width, y, + x + width, y + height, + x, y + height, + x, y]; + + + this.buildLine(graphicsData, webGLData); + + graphicsData.points = tempPoints; + } +}; + +/** + * Builds a rounded rectangle to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildRoundedRectangle = function (graphicsData, webGLData) +{ + var rrectData = graphicsData.shape; + var x = rrectData.x; + var y = rrectData.y; + var width = rrectData.width; + var height = rrectData.height; + + var radius = rrectData.radius; + + var recPoints = []; + recPoints.push(x, y + radius); + recPoints = recPoints.concat(this.quadraticBezierCurve(x, y + height - radius, x, y + height, x + radius, y + height)); + recPoints = recPoints.concat(this.quadraticBezierCurve(x + width - radius, y + height, x + width, y + height, x + width, y + height - radius)); + recPoints = recPoints.concat(this.quadraticBezierCurve(x + width, y + radius, x + width, y, x + width - radius, y)); + recPoints = recPoints.concat(this.quadraticBezierCurve(x + radius, y, x, y, x, y + radius)); + + if (graphicsData.fill) + { + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var verts = webGLData.points; + var indices = webGLData.indices; + + var vecPos = verts.length/6; + + var triangles = utils.PolyK.Triangulate(recPoints); + + // + + var i = 0; + for (i = 0; i < triangles.length; i+=3) + { + indices.push(triangles[i] + vecPos); + indices.push(triangles[i] + vecPos); + indices.push(triangles[i+1] + vecPos); + indices.push(triangles[i+2] + vecPos); + indices.push(triangles[i+2] + vecPos); + } + + for (i = 0; i < recPoints.length; i++) + { + verts.push(recPoints[i], recPoints[++i], r, g, b, alpha); + } + } + + if (graphicsData.lineWidth) + { + var tempPoints = graphicsData.points; + + graphicsData.points = recPoints; + + this.buildLine(graphicsData, webGLData); + + graphicsData.points = tempPoints; + } +}; + +/** + * Calculate the points for a quadratic bezier curve. (helper function..) + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @private + * @param fromX {number} Origin point x + * @param fromY {number} Origin point x + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {number[]} + */ +GraphicsRenderer.prototype.quadraticBezierCurve = function (fromX, fromY, cpX, cpY, toX, toY) +{ + + var xa, + ya, + xb, + yb, + x, + y, + n = 20, + points = []; + + function getPt(n1 , n2, perc) { + var diff = n2 - n1; + + return n1 + ( diff * perc ); + } + + var j = 0; + for (var i = 0; i <= n; i++ ) { + j = i / n; + + // The Green Line + xa = getPt( fromX , cpX , j ); + ya = getPt( fromY , cpY , j ); + xb = getPt( cpX , toX , j ); + yb = getPt( cpY , toY , j ); + + // The Black Dot + x = getPt( xa , xb , j ); + y = getPt( ya , yb , j ); + + points.push(x, y); + } + return points; +}; + +/** + * Builds a circle to draw + * + * @private + * @param graphicsData {Graphics} The graphics object to draw + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildCircle = function (graphicsData, webGLData) +{ + // need to convert points to a nice regular data + var circleData = graphicsData.shape; + var x = circleData.x; + var y = circleData.y; + var width; + var height; + + // TODO - bit hacky?? + if (graphicsData.type === CONST.SHAPES.CIRC) + { + width = circleData.radius; + height = circleData.radius; + } + else + { + width = circleData.width; + height = circleData.height; + } + + var totalSegs = 40; + var seg = (Math.PI * 2) / totalSegs ; + + var i = 0; + + if (graphicsData.fill) + { + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var verts = webGLData.points; + var indices = webGLData.indices; + + var vecPos = verts.length/6; + + indices.push(vecPos); + + for (i = 0; i < totalSegs + 1 ; i++) + { + verts.push(x,y, r, g, b, alpha); + + verts.push(x + Math.sin(seg * i) * width, + y + Math.cos(seg * i) * height, + r, g, b, alpha); + + indices.push(vecPos++, vecPos++); + } + + indices.push(vecPos-1); + } + + if (graphicsData.lineWidth) + { + var tempPoints = graphicsData.points; + + graphicsData.points = []; + + for (i = 0; i < totalSegs + 1; i++) + { + graphicsData.points.push(x + Math.sin(seg * i) * width, + y + Math.cos(seg * i) * height); + } + + this.buildLine(graphicsData, webGLData); + + graphicsData.points = tempPoints; + } +}; + +/** + * Builds a line to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildLine = function (graphicsData, webGLData) +{ + // TODO OPTIMISE! + var i = 0; + var points = graphicsData.points; + + if (points.length === 0) + { + return; + } + + // if the line width is an odd number add 0.5 to align to a whole pixel + if (graphicsData.lineWidth%2) + { + for (i = 0; i < points.length; i++) + { + points[i] += 0.5; + } + } + + // get first and last point.. figure out the middle! + var firstPoint = new math.Point(points[0], points[1]); + var lastPoint = new math.Point(points[points.length - 2], points[points.length - 1]); + + // if the first point is the last point - gonna have issues :) + if (firstPoint.x === lastPoint.x && firstPoint.y === lastPoint.y) + { + // need to clone as we are going to slightly modify the shape.. + points = points.slice(); + + points.pop(); + points.pop(); + + lastPoint = new math.Point(points[points.length - 2], points[points.length - 1]); + + var midPointX = lastPoint.x + (firstPoint.x - lastPoint.x) *0.5; + var midPointY = lastPoint.y + (firstPoint.y - lastPoint.y) *0.5; + + points.unshift(midPointX, midPointY); + points.push(midPointX, midPointY); + } + + var verts = webGLData.points; + var indices = webGLData.indices; + var length = points.length / 2; + var indexCount = points.length; + var indexStart = verts.length/6; + + // DRAW the Line + var width = graphicsData.lineWidth / 2; + + // sort color + var color = utils.hex2rgb(graphicsData.lineColor); + var alpha = graphicsData.lineAlpha; + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var px, py, p1x, p1y, p2x, p2y, p3x, p3y; + var perpx, perpy, perp2x, perp2y, perp3x, perp3y; + var a1, b1, c1, a2, b2, c2; + var denom, pdist, dist; + + p1x = points[0]; + p1y = points[1]; + + p2x = points[2]; + p2y = points[3]; + + perpx = -(p1y - p2y); + perpy = p1x - p2x; + + dist = Math.sqrt(perpx*perpx + perpy*perpy); + + perpx /= dist; + perpy /= dist; + perpx *= width; + perpy *= width; + + // start + verts.push(p1x - perpx , p1y - perpy, + r, g, b, alpha); + + verts.push(p1x + perpx , p1y + perpy, + r, g, b, alpha); + + for (i = 1; i < length-1; i++) + { + p1x = points[(i-1)*2]; + p1y = points[(i-1)*2 + 1]; + + p2x = points[(i)*2]; + p2y = points[(i)*2 + 1]; + + p3x = points[(i+1)*2]; + p3y = points[(i+1)*2 + 1]; + + perpx = -(p1y - p2y); + perpy = p1x - p2x; + + dist = Math.sqrt(perpx*perpx + perpy*perpy); + perpx /= dist; + perpy /= dist; + perpx *= width; + perpy *= width; + + perp2x = -(p2y - p3y); + perp2y = p2x - p3x; + + dist = Math.sqrt(perp2x*perp2x + perp2y*perp2y); + perp2x /= dist; + perp2y /= dist; + perp2x *= width; + perp2y *= width; + + a1 = (-perpy + p1y) - (-perpy + p2y); + b1 = (-perpx + p2x) - (-perpx + p1x); + c1 = (-perpx + p1x) * (-perpy + p2y) - (-perpx + p2x) * (-perpy + p1y); + a2 = (-perp2y + p3y) - (-perp2y + p2y); + b2 = (-perp2x + p2x) - (-perp2x + p3x); + c2 = (-perp2x + p3x) * (-perp2y + p2y) - (-perp2x + p2x) * (-perp2y + p3y); + + denom = a1*b2 - a2*b1; + + if (Math.abs(denom) < 0.1 ) + { + + denom+=10.1; + verts.push(p2x - perpx , p2y - perpy, + r, g, b, alpha); + + verts.push(p2x + perpx , p2y + perpy, + r, g, b, alpha); + + continue; + } + + px = (b1*c2 - b2*c1)/denom; + py = (a2*c1 - a1*c2)/denom; + + + pdist = (px -p2x) * (px -p2x) + (py -p2y) + (py -p2y); + + + if (pdist > 140 * 140) + { + perp3x = perpx - perp2x; + perp3y = perpy - perp2y; + + dist = Math.sqrt(perp3x*perp3x + perp3y*perp3y); + perp3x /= dist; + perp3y /= dist; + perp3x *= width; + perp3y *= width; + + verts.push(p2x - perp3x, p2y -perp3y); + verts.push(r, g, b, alpha); + + verts.push(p2x + perp3x, p2y +perp3y); + verts.push(r, g, b, alpha); + + verts.push(p2x - perp3x, p2y -perp3y); + verts.push(r, g, b, alpha); + + indexCount++; + } + else + { + + verts.push(px , py); + verts.push(r, g, b, alpha); + + verts.push(p2x - (px-p2x), p2y - (py - p2y)); + verts.push(r, g, b, alpha); + } + } + + p1x = points[(length-2)*2]; + p1y = points[(length-2)*2 + 1]; + + p2x = points[(length-1)*2]; + p2y = points[(length-1)*2 + 1]; + + perpx = -(p1y - p2y); + perpy = p1x - p2x; + + dist = Math.sqrt(perpx*perpx + perpy*perpy); + perpx /= dist; + perpy /= dist; + perpx *= width; + perpy *= width; + + verts.push(p2x - perpx , p2y - perpy); + verts.push(r, g, b, alpha); + + verts.push(p2x + perpx , p2y + perpy); + verts.push(r, g, b, alpha); + + indices.push(indexStart); + + for (i = 0; i < indexCount; i++) + { + indices.push(indexStart++); + } + + indices.push(indexStart-1); +}; + +/** + * Builds a complex polygon to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildComplexPoly = function (graphicsData, webGLData) +{ + //TODO - no need to copy this as it gets turned into a FLoat32Array anyways.. + var points = graphicsData.points.slice(); + + if (points.length < 6) + { + return; + } + + // get first and last point.. figure out the middle! + var indices = webGLData.indices; + webGLData.points = points; + webGLData.alpha = graphicsData.fillAlpha; + webGLData.color = utils.hex2rgb(graphicsData.fillColor); + + // calclate the bounds.. + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + var x,y; + + // get size.. + for (var i = 0; i < points.length; i+=2) + { + x = points[i]; + y = points[i+1]; + + minX = x < minX ? x : minX; + maxX = x > maxX ? x : maxX; + + minY = y < minY ? y : minY; + maxY = y > maxY ? y : maxY; + } + + // add a quad to the end cos there is no point making another buffer! + points.push(minX, minY, + maxX, minY, + maxX, maxY, + minX, maxY); + + // push a quad onto the end.. + + //TODO - this aint needed! + var length = points.length / 2; + for (i = 0; i < length; i++) + { + indices.push( i ); + } + +}; + +/** + * Builds a polygon to draw + * + * @private + * @param graphicsData {Graphics} The graphics object containing all the necessary properties + * @param webGLData {object} + */ +GraphicsRenderer.prototype.buildPoly = function (graphicsData, webGLData) +{ + var points = graphicsData.points; + + if (points.length < 6) + { + return; + } + + // get first and last point.. figure out the middle! + var verts = webGLData.points; + var indices = webGLData.indices; + + var length = points.length / 2; + + // sort color + var color = utils.hex2rgb(graphicsData.fillColor); + var alpha = graphicsData.fillAlpha; + var r = color[0] * alpha; + var g = color[1] * alpha; + var b = color[2] * alpha; + + var triangles = utils.PolyK.Triangulate(points); + + if (!triangles) { + return false; + } + + var vertPos = verts.length / 6; + + var i = 0; + + for (i = 0; i < triangles.length; i+=3) + { + indices.push(triangles[i] + vertPos); + indices.push(triangles[i] + vertPos); + indices.push(triangles[i+1] + vertPos); + indices.push(triangles[i+2] +vertPos); + indices.push(triangles[i+2] + vertPos); + } + + for (i = 0; i < length; i++) + { + verts.push(points[i * 2], points[i * 2 + 1], + r, g, b, alpha); + } + + return true; +}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 547bdc7..b0189c5 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -1,6 +1,4 @@ -var SpriteRenderer = require('./utils/SpriteRenderer'), - GraphicsRenderer = require('./utils/GraphicsRenderer'), - WebGLFastSpriteBatch = require('./utils/WebGLFastSpriteBatch'), +var WebGLFastSpriteBatch = require('./utils/WebGLFastSpriteBatch'), WebGLShaderManager = require('./managers/WebGLShaderManager'), WebGLMaskManager = require('./managers/WebGLMaskManager'), WebGLFilterManager = require('./managers/WebGLFilterManager'), @@ -28,21 +26,21 @@ * @param [options.preserveDrawingBuffer=false] {boolean} enables drawing buffer preservation, enable this if you need to call toDataUrl on the webgl context * @param [options.resolution=1] {number} the resolution of the renderer retina would be 2 */ -function WebGLRenderer(width, height, options) +function WebGLRenderer(width, height, options) { utils.sayHello('webGL'); - if (options) + if (options) { - for (var i in CONST.defaultRenderOptions) + for (var i in CONST.defaultRenderOptions) { - if (typeof options[i] === 'undefined') + if (typeof options[i] === 'undefined') { options[i] = CONST.defaultRenderOptions[i]; } } } - else + else { options = CONST.defaultRenderOptions; } @@ -191,14 +189,6 @@ /** * Manages the rendering of sprites - * @member {SpriteRenderer} - */ - this.spriteRenderer = new SpriteRenderer(this); - - this.graphicsRenderer = new GraphicsRenderer(this); - - /** - * Manages the rendering of sprites * @member {WebGLFastSpriteBatch} */ this.fastSpriteBatch = new WebGLFastSpriteBatch(this); @@ -245,8 +235,17 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - this.currentRenderer = this.spriteRenderer; + /** + * Manages the renderer of specific objects. + * + * @member {object} + */ + this.objectRenderers = {}; + // create an instance of each registered object renderer + for (var o in WebGLRenderer._objectRenderers) { + this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); + } } // constructor @@ -255,21 +254,20 @@ utils.eventTarget.mixin(WebGLRenderer.prototype); -Object.defineProperties(WebGLRenderer.prototype, -{ +Object.defineProperties(WebGLRenderer.prototype, { /** * The background color to fill if not transparent * * @member {number} * @memberof WebGLRenderer# */ - backgroundColor: + backgroundColor: { - get: function () + get: function () { return this._backgroundColor; }, - set: function (val) + set: function (val) { this._backgroundColor = val; utils.hex2rgb(val, this._backgroundColorRgb); @@ -281,12 +279,12 @@ * * @private */ -WebGLRenderer.prototype._initContext = function () +WebGLRenderer.prototype._initContext = function () { var gl = this.view.getContext('webgl', this._contextOptions) || this.view.getContext('experimental-webgl', this._contextOptions); this.gl = gl; - if (!gl) + if (!gl) { // fail, not able to get a context throw new Error('This browser does not support webGL. Try using the canvas renderer'); @@ -312,10 +310,10 @@ * * @param object {DisplayObject} the object to be rendered */ -WebGLRenderer.prototype.render = function (object) +WebGLRenderer.prototype.render = function (object) { // no point rendering if our context has been blown up! - if (this.gl.isContextLost()) + if (this.gl.isContextLost()) { return; } @@ -336,13 +334,13 @@ // make sure we are bound to the main frame buffer gl.bindFramebuffer(gl.FRAMEBUFFER, null); - if (this.clearBeforeRender) + if (this.clearBeforeRender) { - if (this.transparent) + if (this.transparent) { gl.clearColor(0, 0, 0, 0); } - else + else { gl.clearColor(this._backgroundColorRgb[0], this._backgroundColorRgb[1], this._backgroundColorRgb[2], 1); } @@ -360,7 +358,7 @@ * @param projection {Point} The projection * @param buffer {Array} a standard WebGL buffer */ -WebGLRenderer.prototype.renderDisplayObject = function (displayObject, projection, buffer) +WebGLRenderer.prototype.renderDisplayObject = function (displayObject, projection, buffer) { this.blendModeManager.setBlendMode(CONST.blendModes.NORMAL); @@ -386,9 +384,9 @@ this.currentRenderer.flush(); }; -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) +WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) { - if(this.currentRenderer === objectRenderer) + if (this.currentRenderer === objectRenderer) { return; } @@ -396,7 +394,7 @@ this.currentRenderer.stop(); this.currentRenderer = objectRenderer; this.currentRenderer.start(); -} +}; /** * Resizes the webGL view to the specified width and height. @@ -404,7 +402,7 @@ * @param width {number} the new width of the webGL view * @param height {number} the new height of the webGL view */ -WebGLRenderer.prototype.resize = function (width, height) +WebGLRenderer.prototype.resize = function (width, height) { this.width = width * this.resolution; this.height = height * this.resolution; @@ -412,7 +410,7 @@ this.view.width = this.width; this.view.height = this.height; - if (this.autoResize) + if (this.autoResize) { this.view.style.width = this.width / this.resolution + 'px'; this.view.style.height = this.height / this.resolution + 'px'; @@ -429,18 +427,18 @@ * * @param texture {BaseTexture|Texture} the texture to update */ -WebGLRenderer.prototype.updateTexture = function (texture) +WebGLRenderer.prototype.updateTexture = function (texture) { texture = texture.baseTexture || texture; - if (!texture.hasLoaded) + if (!texture.hasLoaded) { return; } var gl = this.gl; - if (!texture._glTextures[gl.id]) + if (!texture._glTextures[gl.id]) { texture._glTextures[gl.id] = gl.createTexture(); texture.on('update', this._boundUpdateTexture); @@ -455,22 +453,22 @@ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, texture.scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); - if (texture.mipmap && utils.isPowerOfTwo(texture.width, texture.height)) + if (texture.mipmap && utils.isPowerOfTwo(texture.width, texture.height)) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR_MIPMAP_LINEAR : gl.NEAREST_MIPMAP_NEAREST); gl.generateMipmap(gl.TEXTURE_2D); } - else + else { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, texture.scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); } - if (!texture._powerOf2) + if (!texture._powerOf2) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); } - else + else { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); @@ -479,16 +477,16 @@ return texture._glTextures[gl.id]; }; -WebGLRenderer.prototype.destroyTexture = function (texture) +WebGLRenderer.prototype.destroyTexture = function (texture) { texture = texture.baseTexture || texture; - if (!texture.hasLoaded) + if (!texture.hasLoaded) { return; } - if (texture._glTextures[this.gl.id]) + if (texture._glTextures[this.gl.id]) { this.gl.deleteTexture(texture._glTextures[this.gl.id]); } @@ -500,7 +498,7 @@ * @param event {Event} * @private */ -WebGLRenderer.prototype.handleContextLost = function (event) +WebGLRenderer.prototype.handleContextLost = function (event) { event.preventDefault(); }; @@ -511,12 +509,12 @@ * @param event {Event} * @private */ -WebGLRenderer.prototype.handleContextRestored = function () +WebGLRenderer.prototype.handleContextRestored = function () { this._initContext(); // empty all the ol gl textures as they are useless now - for (var key in utils.TextureCache) + for (var key in utils.TextureCache) { var texture = utils.TextureCache[key].baseTexture; texture._glTextures = []; @@ -528,9 +526,9 @@ * * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ -WebGLRenderer.prototype.destroy = function (removeView) +WebGLRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parent) + if (removeView && this.view.parent) { this.view.parent.removeChild(this.view); } @@ -541,10 +539,15 @@ // time to create the render managers! each one focuses on managine a state in webGL this.shaderManager.destroy(); - this.spriteRenderer.destroy(); this.maskManager.destroy(); this.filterManager.destroy(); + for (var o in this.objectRenderers) { + this.objectRenderers[o].destroy(); + this.objectRenderers[o] = null; + } + + this.objectRenderers = null; // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -574,7 +577,6 @@ this.drawCount = 0; this.shaderManager = null; - this.spriteRenderer = null; this.maskManager = null; this.filterManager = null; this.stencilManager = null; @@ -591,11 +593,11 @@ * * @private */ -WebGLRenderer.prototype._mapBlendModes = function () +WebGLRenderer.prototype._mapBlendModes = function () { var gl = this.gl; - if (!this.blendModes) + if (!this.blendModes) { this.blendModes = {}; @@ -620,3 +622,9 @@ }; WebGLRenderer.glContextId = 0; +WebGLRenderer._objectRenderers = {}; + +WebGLRenderer.registerObjectRenderer = function (name, ctor) { + WebGLRenderer._objectRenderers[name] = ctor; +}; + diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js index 773cc5d..e81ca56 100644 --- a/src/core/renderers/webgl/managers/WebGLMaskManager.js +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -20,11 +20,11 @@ */ WebGLMaskManager.prototype.pushMask = function (maskData) { - this.renderer.setObjectRenderer( this.renderer.graphicsRenderer ); + this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); if (maskData.dirty) { - this.renderer.graphicsRenderer.updateGraphics(maskData, this.renderer.gl); + this.renderer.objectRenderers.graphics.updateGraphics(maskData, this.renderer.gl); } if (!maskData._webGL[this.renderer.gl.id].data.length) @@ -42,7 +42,7 @@ */ WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.setObjectRenderer( this.renderer.graphicsRenderer ); - + this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.stencilManager.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); }; diff --git a/src/core/renderers/webgl/utils/GraphicsRenderer.js b/src/core/renderers/webgl/utils/GraphicsRenderer.js deleted file mode 100644 index 6713275..0000000 --- a/src/core/renderers/webgl/utils/GraphicsRenderer.js +++ /dev/null @@ -1,894 +0,0 @@ -var utils = require('../../../utils'), - math = require('../../../math'), - CONST = require('../../../const'), - WebGLGraphicsData = require('./WebGLGraphicsData'); - -/** - * A set of functions used by the webGL renderer to draw the primitive graphics data - * - * @namespace PIXI - * @private - */ -var WebGLGraphics = module.exports = {}; - -/** - * Renders the graphics object - * - * @static - * @private - * @param graphics {Graphics} - * @param renderer {WebGLRenderer} - */ - -var GraphicsRenderer = function(renderer) -{ - this.renderer = renderer; - this.graphicsDataPool = []; -} - -module.exports = GraphicsRenderer; - -GraphicsRenderer.prototype.start = function() -{ - // set the shader.. - -} - -GraphicsRenderer.prototype.stop = function() -{ - // flush! -} - -GraphicsRenderer.prototype.flush = function() -{ - // flush! -} - - - -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var projection = renderer.projection, - offset = renderer.offset, - shader = renderer.shaderManager.primitiveShader, - webGLData; - - if (graphics.dirty) - { - this.updateGraphics(graphics, gl); - } - - var webGL = graphics._webGL[gl.id]; - - // This could be speeded up for sure! - - renderer.blendModeManager.setBlendMode( graphics.blendMode ); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.stencilManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.stencilManager.popStencil(graphics, webGLData, renderer); - } - else - { - webGLData = webGL.data[i]; - - - renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); - shader = renderer.shaderManager.primitiveShader; - gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); - - gl.uniform1f(shader.uniforms.flipY._location, 1); - - gl.uniform2f(shader.uniforms.projectionVector._location, projection.x, -projection.y); - gl.uniform2f(shader.uniforms.offsetVector._location, -offset.x, -offset.y); - - gl.uniform3fv(shader.uniforms.tint._location, utils.hex2rgb(graphics.tint)); - - gl.uniform1f(shader.uniforms.alpha._location, graphics.worldAlpha); - - - gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); - - gl.vertexAttribPointer(shader.attributes.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); - gl.vertexAttribPointer(shader.attributes.aColor, 4, gl.FLOAT, false,4 * 6, 2 * 4); - - // set the index buffer! - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); - gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); - } - } -} - -/** - * Updates the graphics object - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object to update - * @param gl {WebGLContext} the current WebGL drawing context - */ - -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[gl.id]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[gl.id] = {lastIndex:0, data:[], gl:gl}; - } - - // flag the graphics as not dirty as we are about to update it... - graphics.dirty = false; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty) - { - graphics.clearDirty = false; - - // lop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - graphicsData.reset(); - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - if (data.type === CONST.SHAPES.POLY) - { - // need to add the points the the graphics object.. - data.points = data.shape.points.slice(); - if (data.shape.closed) - { - // close the poly if the value is true! - if (data.points[0] !== data.points[data.points.length-2] || data.points[1] !== data.points[data.points.length-1]) - { - data.points.push(data.points[0], data.points[1]); - } - } - - // MAKE SURE WE HAVE THE CORRECT TYPE.. - if (data.fill) - { - if (data.points.length >= 6) - { - if (data.points.length < 6 * 2) - { - webGLData = this.switchMode(webGL, 0); - - var canDrawUsingSimple = this.buildPoly(data, webGLData); - // console.log(canDrawUsingSimple); - - if (!canDrawUsingSimple) - { - // console.log("<>>>") - webGLData = this.switchMode(webGL, 1); - this.buildComplexPoly(data, webGLData); - } - - } - else - { - webGLData = this.switchMode(webGL, 1); - this.buildComplexPoly(data, webGLData); - } - } - } - - if (data.lineWidth > 0) - { - webGLData = this.switchMode(webGL, 0); - this.buildLine(data, webGLData); - } - } - else - { - webGLData = this.switchMode(webGL, 0); - - if (data.type === CONST.SHAPES.RECT) - { - this.buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - this.buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - this.buildRoundedRectangle(data, webGLData); - } - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -} - -/** - * @static - * @private - * @param webGL {WebGLContext} - * @param type {number} - */ -GraphicsRenderer.prototype.switchMode = function (webGL, type) -{ - var webGLData; - - if (!webGL.data.length) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(webGL.gl); - webGLData.mode = type; - webGL.data.push(webGLData); - } - else - { - webGLData = webGL.data[webGL.data.length-1]; - - if (webGLData.mode !== type || type === 1) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(webGL.gl); - webGLData.mode = type; - webGL.data.push(webGLData); - } - } - - webGLData.dirty = true; - - return webGLData; -}; - - - - - - -/** - * Builds a rectangle to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildRectangle = function (graphicsData, webGLData) -{ - // --- // - // need to convert points to a nice regular data - // - var rectData = graphicsData.shape; - var x = rectData.x; - var y = rectData.y; - var width = rectData.width; - var height = rectData.height; - - if (graphicsData.fill) - { - var color = utils.hex2rgb(graphicsData.fillColor); - var alpha = graphicsData.fillAlpha; - - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var verts = webGLData.points; - var indices = webGLData.indices; - - var vertPos = verts.length/6; - - // start - verts.push(x, y); - verts.push(r, g, b, alpha); - - verts.push(x + width, y); - verts.push(r, g, b, alpha); - - verts.push(x , y + height); - verts.push(r, g, b, alpha); - - verts.push(x + width, y + height); - verts.push(r, g, b, alpha); - - // insert 2 dead triangles.. - indices.push(vertPos, vertPos, vertPos+1, vertPos+2, vertPos+3, vertPos+3); - } - - if (graphicsData.lineWidth) - { - var tempPoints = graphicsData.points; - - graphicsData.points = [x, y, - x + width, y, - x + width, y + height, - x, y + height, - x, y]; - - - this.buildLine(graphicsData, webGLData); - - graphicsData.points = tempPoints; - } -}; - -/** - * Builds a rounded rectangle to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildRoundedRectangle = function (graphicsData, webGLData) -{ - var rrectData = graphicsData.shape; - var x = rrectData.x; - var y = rrectData.y; - var width = rrectData.width; - var height = rrectData.height; - - var radius = rrectData.radius; - - var recPoints = []; - recPoints.push(x, y + radius); - recPoints = recPoints.concat(this.quadraticBezierCurve(x, y + height - radius, x, y + height, x + radius, y + height)); - recPoints = recPoints.concat(this.quadraticBezierCurve(x + width - radius, y + height, x + width, y + height, x + width, y + height - radius)); - recPoints = recPoints.concat(this.quadraticBezierCurve(x + width, y + radius, x + width, y, x + width - radius, y)); - recPoints = recPoints.concat(this.quadraticBezierCurve(x + radius, y, x, y, x, y + radius)); - - if (graphicsData.fill) - { - var color = utils.hex2rgb(graphicsData.fillColor); - var alpha = graphicsData.fillAlpha; - - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var verts = webGLData.points; - var indices = webGLData.indices; - - var vecPos = verts.length/6; - - var triangles = utils.PolyK.Triangulate(recPoints); - - // - - var i = 0; - for (i = 0; i < triangles.length; i+=3) - { - indices.push(triangles[i] + vecPos); - indices.push(triangles[i] + vecPos); - indices.push(triangles[i+1] + vecPos); - indices.push(triangles[i+2] + vecPos); - indices.push(triangles[i+2] + vecPos); - } - - for (i = 0; i < recPoints.length; i++) - { - verts.push(recPoints[i], recPoints[++i], r, g, b, alpha); - } - } - - if (graphicsData.lineWidth) - { - var tempPoints = graphicsData.points; - - graphicsData.points = recPoints; - - this.buildLine(graphicsData, webGLData); - - graphicsData.points = tempPoints; - } -}; - -/** - * Calculate the points for a quadratic bezier curve. (helper function..) - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @static - * @private - * @param fromX {number} Origin point x - * @param fromY {number} Origin point x - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {number[]} - */ -GraphicsRenderer.prototype.quadraticBezierCurve = function (fromX, fromY, cpX, cpY, toX, toY) -{ - - var xa, - ya, - xb, - yb, - x, - y, - n = 20, - points = []; - - function getPt(n1 , n2, perc) { - var diff = n2 - n1; - - return n1 + ( diff * perc ); - } - - var j = 0; - for (var i = 0; i <= n; i++ ) { - j = i / n; - - // The Green Line - xa = getPt( fromX , cpX , j ); - ya = getPt( fromY , cpY , j ); - xb = getPt( cpX , toX , j ); - yb = getPt( cpY , toY , j ); - - // The Black Dot - x = getPt( xa , xb , j ); - y = getPt( ya , yb , j ); - - points.push(x, y); - } - return points; -}; - -/** - * Builds a circle to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object to draw - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildCircle = function (graphicsData, webGLData) -{ - // need to convert points to a nice regular data - var circleData = graphicsData.shape; - var x = circleData.x; - var y = circleData.y; - var width; - var height; - - // TODO - bit hacky?? - if (graphicsData.type === CONST.SHAPES.CIRC) - { - width = circleData.radius; - height = circleData.radius; - } - else - { - width = circleData.width; - height = circleData.height; - } - - var totalSegs = 40; - var seg = (Math.PI * 2) / totalSegs ; - - var i = 0; - - if (graphicsData.fill) - { - var color = utils.hex2rgb(graphicsData.fillColor); - var alpha = graphicsData.fillAlpha; - - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var verts = webGLData.points; - var indices = webGLData.indices; - - var vecPos = verts.length/6; - - indices.push(vecPos); - - for (i = 0; i < totalSegs + 1 ; i++) - { - verts.push(x,y, r, g, b, alpha); - - verts.push(x + Math.sin(seg * i) * width, - y + Math.cos(seg * i) * height, - r, g, b, alpha); - - indices.push(vecPos++, vecPos++); - } - - indices.push(vecPos-1); - } - - if (graphicsData.lineWidth) - { - var tempPoints = graphicsData.points; - - graphicsData.points = []; - - for (i = 0; i < totalSegs + 1; i++) - { - graphicsData.points.push(x + Math.sin(seg * i) * width, - y + Math.cos(seg * i) * height); - } - - this.buildLine(graphicsData, webGLData); - - graphicsData.points = tempPoints; - } -}; - -/** - * Builds a line to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildLine = function (graphicsData, webGLData) -{ - // TODO OPTIMISE! - var i = 0; - var points = graphicsData.points; - - if (points.length === 0) - { - return; - } - - // if the line width is an odd number add 0.5 to align to a whole pixel - if (graphicsData.lineWidth%2) - { - for (i = 0; i < points.length; i++) - { - points[i] += 0.5; - } - } - - // get first and last point.. figure out the middle! - var firstPoint = new math.Point(points[0], points[1]); - var lastPoint = new math.Point(points[points.length - 2], points[points.length - 1]); - - // if the first point is the last point - gonna have issues :) - if (firstPoint.x === lastPoint.x && firstPoint.y === lastPoint.y) - { - // need to clone as we are going to slightly modify the shape.. - points = points.slice(); - - points.pop(); - points.pop(); - - lastPoint = new math.Point(points[points.length - 2], points[points.length - 1]); - - var midPointX = lastPoint.x + (firstPoint.x - lastPoint.x) *0.5; - var midPointY = lastPoint.y + (firstPoint.y - lastPoint.y) *0.5; - - points.unshift(midPointX, midPointY); - points.push(midPointX, midPointY); - } - - var verts = webGLData.points; - var indices = webGLData.indices; - var length = points.length / 2; - var indexCount = points.length; - var indexStart = verts.length/6; - - // DRAW the Line - var width = graphicsData.lineWidth / 2; - - // sort color - var color = utils.hex2rgb(graphicsData.lineColor); - var alpha = graphicsData.lineAlpha; - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var px, py, p1x, p1y, p2x, p2y, p3x, p3y; - var perpx, perpy, perp2x, perp2y, perp3x, perp3y; - var a1, b1, c1, a2, b2, c2; - var denom, pdist, dist; - - p1x = points[0]; - p1y = points[1]; - - p2x = points[2]; - p2y = points[3]; - - perpx = -(p1y - p2y); - perpy = p1x - p2x; - - dist = Math.sqrt(perpx*perpx + perpy*perpy); - - perpx /= dist; - perpy /= dist; - perpx *= width; - perpy *= width; - - // start - verts.push(p1x - perpx , p1y - perpy, - r, g, b, alpha); - - verts.push(p1x + perpx , p1y + perpy, - r, g, b, alpha); - - for (i = 1; i < length-1; i++) - { - p1x = points[(i-1)*2]; - p1y = points[(i-1)*2 + 1]; - - p2x = points[(i)*2]; - p2y = points[(i)*2 + 1]; - - p3x = points[(i+1)*2]; - p3y = points[(i+1)*2 + 1]; - - perpx = -(p1y - p2y); - perpy = p1x - p2x; - - dist = Math.sqrt(perpx*perpx + perpy*perpy); - perpx /= dist; - perpy /= dist; - perpx *= width; - perpy *= width; - - perp2x = -(p2y - p3y); - perp2y = p2x - p3x; - - dist = Math.sqrt(perp2x*perp2x + perp2y*perp2y); - perp2x /= dist; - perp2y /= dist; - perp2x *= width; - perp2y *= width; - - a1 = (-perpy + p1y) - (-perpy + p2y); - b1 = (-perpx + p2x) - (-perpx + p1x); - c1 = (-perpx + p1x) * (-perpy + p2y) - (-perpx + p2x) * (-perpy + p1y); - a2 = (-perp2y + p3y) - (-perp2y + p2y); - b2 = (-perp2x + p2x) - (-perp2x + p3x); - c2 = (-perp2x + p3x) * (-perp2y + p2y) - (-perp2x + p2x) * (-perp2y + p3y); - - denom = a1*b2 - a2*b1; - - if (Math.abs(denom) < 0.1 ) - { - - denom+=10.1; - verts.push(p2x - perpx , p2y - perpy, - r, g, b, alpha); - - verts.push(p2x + perpx , p2y + perpy, - r, g, b, alpha); - - continue; - } - - px = (b1*c2 - b2*c1)/denom; - py = (a2*c1 - a1*c2)/denom; - - - pdist = (px -p2x) * (px -p2x) + (py -p2y) + (py -p2y); - - - if (pdist > 140 * 140) - { - perp3x = perpx - perp2x; - perp3y = perpy - perp2y; - - dist = Math.sqrt(perp3x*perp3x + perp3y*perp3y); - perp3x /= dist; - perp3y /= dist; - perp3x *= width; - perp3y *= width; - - verts.push(p2x - perp3x, p2y -perp3y); - verts.push(r, g, b, alpha); - - verts.push(p2x + perp3x, p2y +perp3y); - verts.push(r, g, b, alpha); - - verts.push(p2x - perp3x, p2y -perp3y); - verts.push(r, g, b, alpha); - - indexCount++; - } - else - { - - verts.push(px , py); - verts.push(r, g, b, alpha); - - verts.push(p2x - (px-p2x), p2y - (py - p2y)); - verts.push(r, g, b, alpha); - } - } - - p1x = points[(length-2)*2]; - p1y = points[(length-2)*2 + 1]; - - p2x = points[(length-1)*2]; - p2y = points[(length-1)*2 + 1]; - - perpx = -(p1y - p2y); - perpy = p1x - p2x; - - dist = Math.sqrt(perpx*perpx + perpy*perpy); - perpx /= dist; - perpy /= dist; - perpx *= width; - perpy *= width; - - verts.push(p2x - perpx , p2y - perpy); - verts.push(r, g, b, alpha); - - verts.push(p2x + perpx , p2y + perpy); - verts.push(r, g, b, alpha); - - indices.push(indexStart); - - for (i = 0; i < indexCount; i++) - { - indices.push(indexStart++); - } - - indices.push(indexStart-1); -}; - -/** - * Builds a complex polygon to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildComplexPoly = function (graphicsData, webGLData) -{ - //TODO - no need to copy this as it gets turned into a FLoat32Array anyways.. - var points = graphicsData.points.slice(); - - if (points.length < 6) - { - return; - } - - // get first and last point.. figure out the middle! - var indices = webGLData.indices; - webGLData.points = points; - webGLData.alpha = graphicsData.fillAlpha; - webGLData.color = utils.hex2rgb(graphicsData.fillColor); - - // calclate the bounds.. - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - var x,y; - - // get size.. - for (var i = 0; i < points.length; i+=2) - { - x = points[i]; - y = points[i+1]; - - minX = x < minX ? x : minX; - maxX = x > maxX ? x : maxX; - - minY = y < minY ? y : minY; - maxY = y > maxY ? y : maxY; - } - - // add a quad to the end cos there is no point making another buffer! - points.push(minX, minY, - maxX, minY, - maxX, maxY, - minX, maxY); - - // push a quad onto the end.. - - //TODO - this aint needed! - var length = points.length / 2; - for (i = 0; i < length; i++) - { - indices.push( i ); - } - -}; - -/** - * Builds a polygon to draw - * - * @static - * @private - * @param graphicsData {Graphics} The graphics object containing all the necessary properties - * @param webGLData {object} - */ -GraphicsRenderer.prototype.buildPoly = function (graphicsData, webGLData) -{ - var points = graphicsData.points; - - if (points.length < 6) - { - return; - } - - // get first and last point.. figure out the middle! - var verts = webGLData.points; - var indices = webGLData.indices; - - var length = points.length / 2; - - // sort color - var color = utils.hex2rgb(graphicsData.fillColor); - var alpha = graphicsData.fillAlpha; - var r = color[0] * alpha; - var g = color[1] * alpha; - var b = color[2] * alpha; - - var triangles = utils.PolyK.Triangulate(points); - - if (!triangles) { - return false; - } - - var vertPos = verts.length / 6; - - var i = 0; - - for (i = 0; i < triangles.length; i+=3) - { - indices.push(triangles[i] + vertPos); - indices.push(triangles[i] + vertPos); - indices.push(triangles[i+1] + vertPos); - indices.push(triangles[i+2] +vertPos); - indices.push(triangles[i+2] + vertPos); - } - - for (i = 0; i < length; i++) - { - verts.push(points[i * 2], points[i * 2 + 1], - r, g, b, alpha); - } - - return true; -}; - -GraphicsRenderer.graphicsDataPool = []; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js new file mode 100644 index 0000000..2a48af4 --- /dev/null +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -0,0 +1,44 @@ +/** + * + * @class + * @private + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this object renderer works for. + */ +function ObjectRenderer(renderer) +{ + /** + * The renderer used by this object renderer. + * + * @member {WebGLRenderer} + */ + this.renderer = renderer; +} + +ObjectRenderer.prototype.constructor = ObjectRenderer; +module.exports = ObjectRenderer; + +ObjectRenderer.prototype.start = function () +{ + // set the shader.. +} + +ObjectRenderer.prototype.stop = function () +{ + // flush! +} + +ObjectRenderer.prototype.flush = function () +{ + // flush! +} + +ObjectRenderer.prototype.render = function (/* object */) +{ + // render the object +} + +ObjectRenderer.prototype.destroy = function () +{ + this.renderer = null; +} diff --git a/src/core/renderers/webgl/utils/SpriteRenderer.js b/src/core/renderers/webgl/utils/SpriteRenderer.js deleted file mode 100644 index dfb2018..0000000 --- a/src/core/renderers/webgl/utils/SpriteRenderer.js +++ /dev/null @@ -1,525 +0,0 @@ -var TextureUvs = require('../../../textures/TextureUvs'), - SpriteShader = require('../shaders/SpriteShader'); - -/** - * @author Mat Groves - * - * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ - * for creating the original pixi version! - * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer - * - * Heavily inspired by LibGDX's SpriteRenderer: - * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/SpriteRenderer.java - */ - -/** - * - * @class - * @private - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this sprite batch works for. - */ -function SpriteRenderer(renderer) -{ - /** - * - * - * @member {WebGLRenderer} - */ - this.renderer = renderer; - - /** - * - * - * @member {number} - */ - this.vertSize = 5; - - /** - * - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = 2000;//Math.pow(2, 16) / this.vertSize; - - // the total number of bytes in our batch - var numVerts = this.size * 4 * this.vertByteSize; - // the total number of indices in our batch - var numIndices = this.size * 6; - - /** - * Holds the vertices - * - * @member {ArrayBuffer} - */ - this.vertices = new ArrayBuffer(numVerts); - - /** - * View on the vertices as a Float32Array - * - * @member {Float32Array} - */ - this.positions = new Float32Array(this.vertices); - - /** - * View on the vertices as a Uint32Array - * - * @member {Uint32Array} - */ - this.colors = new Uint32Array(this.vertices); - - /** - * Holds the indices - * - * @member {Uint16Array} - */ - this.indices = new Uint16Array(numIndices); - - /** - * - * - * @member {number} - */ - this.lastIndexCount = 0; - - for (var i=0, j=0; i < numIndices; i += 6, j += 4) - { - this.indices[i + 0] = j + 0; - this.indices[i + 1] = j + 1; - this.indices[i + 2] = j + 2; - this.indices[i + 3] = j + 0; - this.indices[i + 4] = j + 2; - this.indices[i + 5] = j + 3; - } - - /** - * - * - * @member {boolean} - */ - this.drawing = false; - - /** - * - * - * @member {number} - */ - this.currentBatchSize = 0; - - /** - * - * - * @member {BaseTexture} - */ - this.currentBaseTexture = null; - - /** - * - * - * @member {boolean} - */ - this.dirty = true; - - /** - * - * - * @member {Array} - */ - this.textures = []; - - /** - * - * - * @member {Array} - */ - this.blendModes = []; - - /** - * - * - * @member {Array} - */ - this.shaders = []; - - /** - * - * - * @member {Array} - */ - this.sprites = []; - - /** - * The default shader that is used if a sprite doesn't have a more specific one. - * - * @member {Shader} - */ - this.shader = null; - - // listen for context and update necessary buffers - var self = this; - this.renderer.on('context', function () - { - self.setupContext(); - }); -} - -SpriteRenderer.prototype.constructor = SpriteRenderer; -module.exports = SpriteRenderer; - -/** - * @param gl {WebGLContext} the current WebGL drawing context - */ -SpriteRenderer.prototype.setupContext = function () -{ - var gl = this.renderer.gl; - - // setup default shader - this.shader = new SpriteShader(gl); - - // create a couple of buffers - this.vertexBuffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - - // 65535 is max index, so 65535 / 6 = 10922. - - //upload the index data - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); - - this.currentBlendMode = 99999; -}; - - -/** - * @param sprite {Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite.texture; - - //TODO set blend modes.. - // check texture.. - if (this.currentBatchSize >= this.size) - { - this.flush(); - this.currentBaseTexture = texture.baseTexture; - } - - // get the uvs for the texture - var uvs = texture._uvs; - - // if the uvs have not updated then no point rendering just yet! - if (!uvs) - { - return; - } - - // TODO trim?? - var aX = sprite.anchor.x; - var aY = sprite.anchor.y; - - var w0, w1, h0, h1; - - if (texture.trim) - { - // if the sprite is trimmed then we need to add the extra space before transforming the sprite coords.. - var trim = texture.trim; - - w1 = trim.x - aX * trim.width; - w0 = w1 + texture.crop.width; - - h1 = trim.y - aY * trim.height; - h0 = h1 + texture.crop.height; - - } - else - { - w0 = (texture.frame.width ) * (1-aX); - w1 = (texture.frame.width ) * -aX; - - h0 = texture.frame.height * (1-aY); - h1 = texture.frame.height * -aY; - } - - var index = this.currentBatchSize * this.vertByteSize; - - var resolution = texture.baseTexture.resolution; - - var worldTransform = sprite.worldTransform; - - var a = worldTransform.a / resolution; - var b = worldTransform.b / resolution; - var c = worldTransform.c / resolution; - var d = worldTransform.d / resolution; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var colors = this.colors; - var positions = this.positions; - - if (this.renderer.roundPixels) - { - // xy - positions[index] = a * w1 + c * h1 + tx | 0; - positions[index+1] = d * h1 + b * w1 + ty | 0; - - // xy - positions[index+5] = a * w0 + c * h1 + tx | 0; - positions[index+6] = d * h1 + b * w0 + ty | 0; - - // xy - positions[index+10] = a * w0 + c * h0 + tx | 0; - positions[index+11] = d * h0 + b * w0 + ty | 0; - - // xy - positions[index+15] = a * w1 + c * h0 + tx | 0; - positions[index+16] = d * h0 + b * w1 + ty | 0; - } - else - { - // xy - positions[index] = a * w1 + c * h1 + tx; - positions[index+1] = d * h1 + b * w1 + ty; - - // xy - positions[index+5] = a * w0 + c * h1 + tx; - positions[index+6] = d * h1 + b * w0 + ty; - - // xy - positions[index+10] = a * w0 + c * h0 + tx; - positions[index+11] = d * h0 + b * w0 + ty; - - // xy - positions[index+15] = a * w1 + c * h0 + tx; - positions[index+16] = d * h0 + b * w1 + ty; - } - - // uv - positions[index+2] = uvs.x0; - positions[index+3] = uvs.y0; - - // uv - positions[index+7] = uvs.x1; - positions[index+8] = uvs.y1; - - // uv - positions[index+12] = uvs.x2; - positions[index+13] = uvs.y2; - - // uv - positions[index+17] = uvs.x3; - positions[index+18] = uvs.y3; - - // color and alpha - var tint = sprite.tint; - colors[index+4] = colors[index+9] = colors[index+14] = colors[index+19] = (tint >> 16) + (tint & 0xff00) + ((tint & 0xff) << 16) + (sprite.worldAlpha * 255 << 24); - - // increment the batchsize - this.sprites[this.currentBatchSize++] = sprite; - - -}; - - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - // If the batch is length 0 then return as there is nothing to draw - if (this.currentBatchSize === 0) - { - return; - } - - var gl = this.renderer.gl; - var shader; - - if (this.dirty) - { - this.dirty = false; - // bind the main texture - gl.activeTexture(gl.TEXTURE0); - - // bind the buffers - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - - // this is the same for each shader? - var stride = this.vertByteSize; - gl.vertexAttribPointer(this.shader.attributes.aVertexPosition, 2, gl.FLOAT, false, stride, 0); - gl.vertexAttribPointer(this.shader.attributes.aTextureCoord, 2, gl.FLOAT, false, stride, 2 * 4); - - // color attributes will be interpreted as unsigned bytes and normalized - gl.vertexAttribPointer(this.shader.attributes.aColor, 4, gl.UNSIGNED_BYTE, true, stride, 4 * 4); - } - - // upload the verts to the buffer - if (this.currentBatchSize > ( this.size * 0.5 ) ) - { - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertices); - } - else - { - var view = this.positions.subarray(0, this.currentBatchSize * this.vertByteSize); - gl.bufferSubData(gl.ARRAY_BUFFER, 0, view); - } - - var nextTexture, nextBlendMode, nextShader; - var batchSize = 0; - var start = 0; - - var currentBaseTexture = null; - var currentBlendMode = this.renderer.blendModeManager.currentBlendMode; - var currentShader = null; - - var blendSwap = false; - var shaderSwap = false; - var sprite; - - for (var i = 0, j = this.currentBatchSize; i < j; i++) - { - - sprite = this.sprites[i]; - - nextTexture = sprite.texture.baseTexture; - nextBlendMode = sprite.blendMode; - nextShader = sprite.shader || this.shader; - - blendSwap = currentBlendMode !== nextBlendMode; - shaderSwap = currentShader !== nextShader; // should I use uuidS??? - - if (currentBaseTexture !== nextTexture || blendSwap || shaderSwap) - { - this.renderBatch(currentBaseTexture, batchSize, start); - - start = i; - batchSize = 0; - currentBaseTexture = nextTexture; - - if (blendSwap) - { - currentBlendMode = nextBlendMode; - this.renderer.blendModeManager.setBlendMode( currentBlendMode ); - } - - if (shaderSwap) - { - currentShader = nextShader; - - shader = currentShader.shaders ? currentShader.shaders[gl.id] : currentShader; - - if (!shader) - { - shader = new Shader(gl, null, currentShader.fragmentSrc, currentShader.uniforms); - currentShader.shaders[gl.id] = shader; - } - - // set shader function??? - this.renderer.shaderManager.setShader(shader); - - if (shader.dirty) - { - shader.syncUniforms(); - } - - // both thease only need to be set if they are changing.. - // set the projection - var projection = this.renderer.projection; - gl.uniform2f(shader.uniforms.projectionVector._location, projection.x, projection.y); - - // TODO - this is temprorary! - var offsetVector = this.renderer.offset; - gl.uniform2f(shader.uniforms.offsetVector._location, offsetVector.x, offsetVector.y); - - // set the pointers - } - } - - batchSize++; - } - - this.renderBatch(currentBaseTexture, batchSize, start); - - // then reset the batch! - this.currentBatchSize = 0; -}; - -/** - * @param texture {Texture} - * @param size {number} - * @param startIndex {number} - */ -SpriteRenderer.prototype.renderBatch = function (texture, size, startIndex) -{ - if (size === 0) - { - return; - } - - var gl = this.renderer.gl; - - if (!texture._glTextures[gl.id]) - { - this.renderer.updateTexture(texture); - } - else - { - // bind the current texture - gl.bindTexture(gl.TEXTURE_2D, texture._glTextures[gl.id]); - } - - // now draw those suckas! - gl.drawElements(gl.TRIANGLES, size * 6, gl.UNSIGNED_SHORT, startIndex * 6 * 2); - - // increment the draw count - this.renderer.drawCount++; -}; - -/** - * - */ -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.dirty = true; -}; - -/** - * - */ -SpriteRenderer.prototype.start = function () -{ - this.dirty = true; -}; - -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - this.renderer.gl.deleteBuffer(this.vertexBuffer); - this.renderer.gl.deleteBuffer(this.indexBuffer); - - this.vertices = null; - this.indices = null; - - this.vertexBuffer = null; - this.indexBuffer = null; - - this.currentBaseTexture = null; - - this.renderer = null; -}; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js new file mode 100644 index 0000000..8e3710a --- /dev/null +++ b/src/core/sprites/Sprite.js @@ -0,0 +1,458 @@ +var math = require('../math'), + Texture = require('../textures/Texture'), + DisplayObjectContainer = require('./DisplayObjectContainer'), + CanvasTinter = require('../renderers/canvas/utils/CanvasTinter'), + utils = require('../utils'), + CONST = require('../const'); + +/** + * The Sprite object is the base for all textured objects that are rendered to the screen + * + * A sprite can be created directly from an image like this: + * + * ```js + * var sprite = new Sprite.fromImage('assets/image.png'); + * ``` + * + * @class Sprite + * @extends DisplayObjectContainer + * @namespace PIXI + * @param texture {Texture} The texture for this sprite + */ +function Sprite(texture) +{ + DisplayObjectContainer.call(this); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting than anchor to 0.5,0.5 means the textures origin is centered + * Setting the anchor to 1,1 would mean the textures origin points will be the bottom right corner + * + * @member {Point} + */ + this.anchor = new math.Point(); + + /** + * The texture that the sprite is using + * + * @member {Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Set to CONST.blendModes.NORMAL to remove any blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {AbstractFilter} + */ + this.shader = null; + + this.renderable = true; + + // call texture setter + this.texture = texture || Texture.EMPTY; +} + +Sprite.prototype.destroy = function (destroyTexture, destroyBaseTexture) +{ + DisplayObjectContainer.prototype.destroy.call(this); + + this.anchor = null; + + if (destroyTexture) + { + this._texture.destroy(destroyBaseTexture); + } + + this._texture = null; + this.shader = null; +}; + +// constructor +Sprite.prototype = Object.create(DisplayObjectContainer.prototype); +Sprite.prototype.constructor = Sprite; +module.exports = Sprite; + +Object.defineProperties(Sprite.prototype, { + /** + * The width of the sprite, setting this will actually modify the scale to achieve the value set + * + * @member + * @memberof Sprite# + */ + width: { + get: function () + { + return this.scale.x * this.texture.frame.width; + }, + set: function (value) + { + this.scale.x = value / this.texture.frame.width; + this._width = value; + } + }, + + /** + * The height of the sprite, setting this will actually modify the scale to achieve the value set + * + * @member + * @memberof Sprite# + */ + height: { + get: function () + { + return this.scale.y * this.texture.frame.height; + }, + set: function (value) + { + this.scale.y = value / this.texture.frame.height; + this._height = value; + } + }, + + /** + * The height of the sprite, setting this will actually modify the scale to achieve the value set + * + * @member + * @memberof Sprite# + */ + texture: { + get: function () + { + return this._texture; + }, + set: function (value) + { + if (this._texture === value) + { + return; + } + + this._texture = value; + this.cachedTint = 0xFFFFFF; + + if (value) + { + // wait for the texture to load + if (value.baseTexture.hasLoaded) + { + this._onTextureUpdate(); + } + else + { + value.once('update', this._onTextureUpdate.bind(this)); + } + } + } + }, +}); + +/** + * When the texture is updated, this event will fire to update the scale and frame + * + * @private + */ +Sprite.prototype._onTextureUpdate = function () +{ + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = this._width / this.texture.frame.width; + } + + if (this._height) + { + this.scale.y = this._height / this.texture.frame.height; + } +}; + +Sprite.prototype._renderWebGL = function (renderer) +{ + renderer.setObjectRenderer(renderer.objectRenderer.sprite); + renderer.objectRenderer.sprite.render(this); +}; + +/** + * Returns the bounds of the Sprite as a rectangle. The bounds calculation takes the worldTransform into account. + * + * @param matrix {Matrix} the transformation matrix of the sprite + * @return {Rectangle} the framing rectangle + */ +Sprite.prototype.getBounds = function (matrix) +{ + var width = this.texture.frame.width; + var height = this.texture.frame.height; + + var w0 = width * (1-this.anchor.x); + var w1 = width * -this.anchor.x; + + var h0 = height * (1-this.anchor.y); + var h1 = height * -this.anchor.y; + + var worldTransform = matrix || this.worldTransform ; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var minX, + maxX, + minY, + maxY; + + if(b === 0 && c === 0) + { + // scale may be negative! + if (a < 0) + { + a *= -1; + } + + if (d < 0) + { + d *= -1; + } + + // this means there is no rotation going on right? RIGHT? + // if thats the case then we can avoid checking the bound values! yay + minX = a * w1 + tx; + maxX = a * w0 + tx; + minY = d * h1 + ty; + maxY = d * h0 + ty; + } + else + { + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + minX = x1; + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y1; + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x1; + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y1; + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + } + + var bounds = this._bounds; + + bounds.x = minX; + bounds.width = maxX - minX; + + bounds.y = minY; + bounds.height = maxY - minY; + + // store a reference so that if this function gets called again in the render cycle we do not have to recalculate + this._currentBounds = bounds; + + return bounds; +}; + +/** +* Renders the object using the Canvas renderer +* +* @param renderer {CanvasRenderer} The renderer +*/ +Sprite.prototype.renderCanvas = function (renderer) +{ + if (!this.visible || this.alpha <= 0 || this.texture.crop.width <= 0 || this.texture.crop.height <= 0) + { + return; + } + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + renderer.context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + // Ignore null sources + if (this.texture.valid) + { + var resolution = this.texture.baseTexture.resolution / renderer.resolution; + + renderer.context.globalAlpha = this.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for this texture + if (renderer.smoothProperty && renderer.scaleMode !== this.texture.baseTexture.scaleMode) + { + renderer.scaleMode = this.texture.baseTexture.scaleMode; + renderer.context[renderer.smoothProperty] = (renderer.scaleMode === CONST.scaleModes.LINEAR); + } + + // If the texture is trimmed we offset by the trim x/y, otherwise we use the frame dimensions + var dx = (this.texture.trim ? this.texture.trim.x : 0) - (this.anchor.x * this.texture.trim.width); + var dy = (this.texture.trim ? this.texture.trim.y : 0) - (this.anchor.y * this.texture.trim.height); + + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + this.worldTransform.a, + this.worldTransform.b, + this.worldTransform.c, + this.worldTransform.d, + (this.worldTransform.tx * renderer.resolution) | 0, + (this.worldTransform.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + this.worldTransform.a, + this.worldTransform.b, + this.worldTransform.c, + this.worldTransform.d, + this.worldTransform.tx * renderer.resolution, + this.worldTransform.ty * renderer.resolution + ); + } + + if (this.tint !== 0xFFFFFF) + { + if (this.cachedTint !== this.tint) + { + this.cachedTint = this.tint; + + // TODO clean up caching - how to clean up the caches? + this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); + } + + renderer.context.drawImage( + this.tintedTexture, + 0, + 0, + this.texture.crop.width, + this.texture.crop.height, + dx / resolution, + dy / resolution, + this.texture.crop.width / resolution, + this.texture.crop.height / resolution + ); + } + else + { + renderer.context.drawImage( + this.texture.baseTexture.source, + this.texture.crop.x, + this.texture.crop.y, + this.texture.crop.width, + this.texture.crop.height, + dx / resolution, + dy / resolution, + this.texture.crop.width / resolution, + this.texture.crop.height / resolution + ); + } + } + + for (var i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } +}; + +// some helper functions.. + +/** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {String} The frame Id of the texture in the cache + * @return {Sprite} A new Sprite using a texture from the texture cache matching the frameId + */ +Sprite.fromFrame = function (frameId) +{ + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache' + this); + } + + return new Sprite(texture); +}; + +/** + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded + * + * @static + * @param imageId {String} The image url of the texture + * @return {Sprite} A new Sprite using a texture from the texture cache matching the image id + */ +Sprite.fromImage = function (imageId, crossorigin, scaleMode) +{ + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); +}; diff --git a/src/core/sprites/SpriteBatch.js b/src/core/sprites/SpriteBatch.js new file mode 100644 index 0000000..c196fef --- /dev/null +++ b/src/core/sprites/SpriteBatch.js @@ -0,0 +1,183 @@ +var DisplayObjectContainer = require('./DisplayObjectContainer'), + WebGLFastSpriteBatch = require('../renderers/webgl/utils/WebGLFastSpriteBatch'); + +/** + * The SpriteBatch class is a really fast version of the DisplayObjectContainer built solely for speed, + * so use when you need a lot of sprites or particles. The tradeoff of the SpriteBatch is that advanced + * functionality will not work. SpriteBatch implements only the basic object transform (position, scale, rotation). + * Any other functionality like tinting, masking, etc will not work on sprites in this batch. + * + * It's extremely easy to use : + * + * ```js + * var container = new SpriteBatch(); + * + * for(var i = 0; i < 100; ++i) + * { + * var sprite = new PIXI.Sprite.fromImage("myImage.png"); + * container.addChild(sprite); + * } + * ``` + * + * And here you have a hundred sprites that will be renderer at the speed of light. + * + * @class + * @namespace PIXI + */ + +//TODO RENAME to PARTICLE CONTAINER? +function SpriteBatch() +{ + DisplayObjectContainer.call(this); +} + +SpriteBatch.prototype = Object.create(DisplayObjectContainer.prototype); +SpriteBatch.prototype.constructor = SpriteBatch; +module.exports = SpriteBatch; + +/** + * Updates the object transform for rendering + * + * @private + */ +SpriteBatch.prototype.updateTransform = function () +{ + // TODO don't need to! + this.displayObjectUpdateTransform(); + // PIXI.DisplayObjectContainer.prototype.updateTransform.call( this ); +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} The webgl renderer + * @private + */ +SpriteBatch.prototype.renderWebGL = function (renderer) +{ + if (!this.visible || this.alpha <= 0 || !this.children.length) + { + return; + } + + renderer.spriteBatch.stop(); + + renderer.shaderManager.setShader(renderer.shaderManager.fastShader); + + renderer.fastSpriteBatch.begin(this); + renderer.fastSpriteBatch.render(this); + + renderer.spriteBatch.start(); +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} The canvas renderer + * @private + */ +SpriteBatch.prototype.renderCanvas = function (renderer) +{ + if (!this.visible || this.alpha <= 0 || !this.children.length) + { + return; + } + + var context = renderer.context; + var transform = this.worldTransform; + var isRotated = true; + + context.globalAlpha = this.worldAlpha; + + this.displayObjectUpdateTransform(); + + for (var i = 0; i < this.children.length; ++i) + { + var child = this.children[i]; + + if (!child.visible) + { + continue; + } + + var frame = child.texture.frame; + + context.globalAlpha = this.worldAlpha * child.alpha; + + if (child.rotation % (Math.PI * 2) === 0) + { + // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call + if (isRotated) + { + context.setTransform( + transform.a, + transform.b, + transform.c, + transform.d, + transform.tx, + transform.ty + ); + + isRotated = false; + } + + context.drawImage( + child.texture.baseTexture.source, + frame.x, + frame.y, + frame.width, + frame.height, + ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5) | 0, + ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5) | 0, + frame.width * child.scale.x, + frame.height * child.scale.y + ); + } + else + { + if (!isRotated) + { + isRotated = true; + } + + child.displayObjectUpdateTransform(); + + var childTransform = child.worldTransform; + + if (renderer.roundPixels) + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx | 0, + childTransform.ty | 0 + ); + } + else + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx, + childTransform.ty + ); + } + + context.drawImage( + child.texture.baseTexture.source, + frame.x, + frame.y, + frame.width, + frame.height, + ((child.anchor.x) * (-frame.width) + 0.5) | 0, + ((child.anchor.y) * (-frame.height) + 0.5) | 0, + frame.width, + frame.height + ); + } + } +}; diff --git a/src/core/sprites/SpriteRenderer.js b/src/core/sprites/SpriteRenderer.js new file mode 100644 index 0000000..2aa2b35 --- /dev/null +++ b/src/core/sprites/SpriteRenderer.js @@ -0,0 +1,545 @@ +var TextureUvs = require('../../../textures/TextureUvs'), + ObjectRenderer = require('../renderers/webgl/utils/ObjectRenderer'), + SpriteShader = require('../renderers/webgl/shaders/SpriteShader'), + WebGLRenderer = require('../renderers/webgl/WebGLRenderer'); + +/** + * @author Mat Groves + * + * Big thanks to the very clever Matt DesLauriers https://github.com/mattdesl/ + * for creating the original pixi version! + * Also a thanks to https://github.com/bchevalier for tweaking the tint and alpha so that they now share 4 bytes on the vertex buffer + * + * Heavily inspired by LibGDX's SpriteRenderer: + * https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/SpriteRenderer.java + */ + +/** + * + * @class + * @private + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this sprite batch works for. + */ +function SpriteRenderer(renderer) +{ + ObjectRenderer.call(this, renderer); + + /** + * + * + * @member {WebGLRenderer} + */ + this.renderer = renderer; + + /** + * + * + * @member {number} + */ + this.vertSize = 5; + + /** + * + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = 2000;//Math.pow(2, 16) / this.vertSize; + + // the total number of bytes in our batch + var numVerts = this.size * 4 * this.vertByteSize; + // the total number of indices in our batch + var numIndices = this.size * 6; + + /** + * Holds the vertices + * + * @member {ArrayBuffer} + */ + this.vertices = new ArrayBuffer(numVerts); + + /** + * View on the vertices as a Float32Array + * + * @member {Float32Array} + */ + this.positions = new Float32Array(this.vertices); + + /** + * View on the vertices as a Uint32Array + * + * @member {Uint32Array} + */ + this.colors = new Uint32Array(this.vertices); + + /** + * Holds the indices + * + * @member {Uint16Array} + */ + this.indices = new Uint16Array(numIndices); + + /** + * + * + * @member {number} + */ + this.lastIndexCount = 0; + + for (var i=0, j=0; i < numIndices; i += 6, j += 4) + { + this.indices[i + 0] = j + 0; + this.indices[i + 1] = j + 1; + this.indices[i + 2] = j + 2; + this.indices[i + 3] = j + 0; + this.indices[i + 4] = j + 2; + this.indices[i + 5] = j + 3; + } + + /** + * + * + * @member {boolean} + */ + this.drawing = false; + + /** + * + * + * @member {number} + */ + this.currentBatchSize = 0; + + /** + * + * + * @member {BaseTexture} + */ + this.currentBaseTexture = null; + + /** + * + * + * @member {boolean} + */ + this.dirty = true; + + /** + * + * + * @member {Array} + */ + this.textures = []; + + /** + * + * + * @member {Array} + */ + this.blendModes = []; + + /** + * + * + * @member {Array} + */ + this.shaders = []; + + /** + * + * + * @member {Array} + */ + this.sprites = []; + + /** + * The default shader that is used if a sprite doesn't have a more specific one. + * + * @member {Shader} + */ + this.shader = null; + + // listen for context and update necessary buffers + var self = this; + this.renderer.on('context', function () + { + self.setupContext(); + }); +} + +SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); +SpriteRenderer.prototype.constructor = SpriteRenderer; +module.exports = SpriteRenderer; + +WebGLRenderer.registerObjectRenderer('sprite', SpriteRenderer); + +/** + * Sets up the renderer context and necessary buffers. + * + * @private + * @param gl {WebGLContext} the current WebGL drawing context + */ +SpriteRenderer.prototype.setupContext = function () +{ + var gl = this.renderer.gl; + + // setup default shader + this.shader = new SpriteShader(gl); + + // create a couple of buffers + this.vertexBuffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + + // 65535 is max index, so 65535 / 6 = 10922. + + //upload the index data + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); + + this.currentBlendMode = 99999; +}; + +/** + * Renders the sprite object. + * + * @param sprite {Sprite} the sprite to render when using this spritebatch + */ +SpriteRenderer.prototype.render = function (sprite) +{ + var texture = sprite.texture; + + //TODO set blend modes.. + // check texture.. + if (this.currentBatchSize >= this.size) + { + this.flush(); + this.currentBaseTexture = texture.baseTexture; + } + + // get the uvs for the texture + var uvs = texture._uvs; + + // if the uvs have not updated then no point rendering just yet! + if (!uvs) + { + return; + } + + // TODO trim?? + var aX = sprite.anchor.x; + var aY = sprite.anchor.y; + + var w0, w1, h0, h1; + + if (texture.trim) + { + // if the sprite is trimmed then we need to add the extra space before transforming the sprite coords.. + var trim = texture.trim; + + w1 = trim.x - aX * trim.width; + w0 = w1 + texture.crop.width; + + h1 = trim.y - aY * trim.height; + h0 = h1 + texture.crop.height; + + } + else + { + w0 = (texture.frame.width ) * (1-aX); + w1 = (texture.frame.width ) * -aX; + + h0 = texture.frame.height * (1-aY); + h1 = texture.frame.height * -aY; + } + + var index = this.currentBatchSize * this.vertByteSize; + + var resolution = texture.baseTexture.resolution; + + var worldTransform = sprite.worldTransform; + + var a = worldTransform.a / resolution; + var b = worldTransform.b / resolution; + var c = worldTransform.c / resolution; + var d = worldTransform.d / resolution; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var colors = this.colors; + var positions = this.positions; + + if (this.renderer.roundPixels) + { + // xy + positions[index] = a * w1 + c * h1 + tx | 0; + positions[index+1] = d * h1 + b * w1 + ty | 0; + + // xy + positions[index+5] = a * w0 + c * h1 + tx | 0; + positions[index+6] = d * h1 + b * w0 + ty | 0; + + // xy + positions[index+10] = a * w0 + c * h0 + tx | 0; + positions[index+11] = d * h0 + b * w0 + ty | 0; + + // xy + positions[index+15] = a * w1 + c * h0 + tx | 0; + positions[index+16] = d * h0 + b * w1 + ty | 0; + } + else + { + // xy + positions[index] = a * w1 + c * h1 + tx; + positions[index+1] = d * h1 + b * w1 + ty; + + // xy + positions[index+5] = a * w0 + c * h1 + tx; + positions[index+6] = d * h1 + b * w0 + ty; + + // xy + positions[index+10] = a * w0 + c * h0 + tx; + positions[index+11] = d * h0 + b * w0 + ty; + + // xy + positions[index+15] = a * w1 + c * h0 + tx; + positions[index+16] = d * h0 + b * w1 + ty; + } + + // uv + positions[index+2] = uvs.x0; + positions[index+3] = uvs.y0; + + // uv + positions[index+7] = uvs.x1; + positions[index+8] = uvs.y1; + + // uv + positions[index+12] = uvs.x2; + positions[index+13] = uvs.y2; + + // uv + positions[index+17] = uvs.x3; + positions[index+18] = uvs.y3; + + // color and alpha + var tint = sprite.tint; + colors[index+4] = colors[index+9] = colors[index+14] = colors[index+19] = (tint >> 16) + (tint & 0xff00) + ((tint & 0xff) << 16) + (sprite.worldAlpha * 255 << 24); + + // increment the batchsize + this.sprites[this.currentBatchSize++] = sprite; +}; + +/** + * Renders the content and empties the current batch. + * + */ +SpriteRenderer.prototype.flush = function () +{ + // If the batch is length 0 then return as there is nothing to draw + if (this.currentBatchSize === 0) + { + return; + } + + var gl = this.renderer.gl; + var shader; + + if (this.dirty) + { + this.dirty = false; + // bind the main texture + gl.activeTexture(gl.TEXTURE0); + + // bind the buffers + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + + // this is the same for each shader? + var stride = this.vertByteSize; + gl.vertexAttribPointer(this.shader.attributes.aVertexPosition, 2, gl.FLOAT, false, stride, 0); + gl.vertexAttribPointer(this.shader.attributes.aTextureCoord, 2, gl.FLOAT, false, stride, 2 * 4); + + // color attributes will be interpreted as unsigned bytes and normalized + gl.vertexAttribPointer(this.shader.attributes.aColor, 4, gl.UNSIGNED_BYTE, true, stride, 4 * 4); + } + + // upload the verts to the buffer + if (this.currentBatchSize > ( this.size * 0.5 ) ) + { + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertices); + } + else + { + var view = this.positions.subarray(0, this.currentBatchSize * this.vertByteSize); + gl.bufferSubData(gl.ARRAY_BUFFER, 0, view); + } + + var nextTexture, nextBlendMode, nextShader; + var batchSize = 0; + var start = 0; + + var currentBaseTexture = null; + var currentBlendMode = this.renderer.blendModeManager.currentBlendMode; + var currentShader = null; + + var blendSwap = false; + var shaderSwap = false; + var sprite; + + for (var i = 0, j = this.currentBatchSize; i < j; i++) + { + + sprite = this.sprites[i]; + + nextTexture = sprite.texture.baseTexture; + nextBlendMode = sprite.blendMode; + nextShader = sprite.shader || this.shader; + + blendSwap = currentBlendMode !== nextBlendMode; + shaderSwap = currentShader !== nextShader; // should I use uuidS??? + + if (currentBaseTexture !== nextTexture || blendSwap || shaderSwap) + { + this.renderBatch(currentBaseTexture, batchSize, start); + + start = i; + batchSize = 0; + currentBaseTexture = nextTexture; + + if (blendSwap) + { + currentBlendMode = nextBlendMode; + this.renderer.blendModeManager.setBlendMode( currentBlendMode ); + } + + if (shaderSwap) + { + currentShader = nextShader; + + shader = currentShader.shaders ? currentShader.shaders[gl.id] : currentShader; + + if (!shader) + { + shader = new Shader(gl, null, currentShader.fragmentSrc, currentShader.uniforms); + currentShader.shaders[gl.id] = shader; + } + + // set shader function??? + this.renderer.shaderManager.setShader(shader); + + if (shader.dirty) + { + shader.syncUniforms(); + } + + // both thease only need to be set if they are changing.. + // set the projection + var projection = this.renderer.projection; + gl.uniform2f(shader.uniforms.projectionVector._location, projection.x, projection.y); + + // TODO - this is temprorary! + var offsetVector = this.renderer.offset; + gl.uniform2f(shader.uniforms.offsetVector._location, offsetVector.x, offsetVector.y); + + // set the pointers + } + } + + batchSize++; + } + + this.renderBatch(currentBaseTexture, batchSize, start); + + // then reset the batch! + this.currentBatchSize = 0; +}; + +/** + * Draws the currently batches sprites. + * + * @private + * @param texture {Texture} + * @param size {number} + * @param startIndex {number} + */ +SpriteRenderer.prototype.renderBatch = function (texture, size, startIndex) +{ + if (size === 0) + { + return; + } + + var gl = this.renderer.gl; + + if (!texture._glTextures[gl.id]) + { + this.renderer.updateTexture(texture); + } + else + { + // bind the current texture + gl.bindTexture(gl.TEXTURE_2D, texture._glTextures[gl.id]); + } + + // now draw those suckas! + gl.drawElements(gl.TRIANGLES, size * 6, gl.UNSIGNED_SHORT, startIndex * 6 * 2); + + // increment the draw count + this.renderer.drawCount++; +}; + +/** + * Flushes the sprite renderer's current batch. + * + */ +SpriteRenderer.prototype.stop = function () +{ + this.flush(); + this.dirty = true; +}; + +/** + * Starts a new sprite batch. + * + */ +SpriteRenderer.prototype.start = function () +{ + this.dirty = true; +}; + +/** + * Destroys the SpriteBatch. + * + */ +SpriteRenderer.prototype.destroy = function () +{ + this.renderer.gl.deleteBuffer(this.vertexBuffer); + this.renderer.gl.deleteBuffer(this.indexBuffer); + + this.renderer = null; + + this.vertices = null; + this.positions = null; + this.colors = null; + this.indices = null; + this.currentBaseTexture = null; + + this.drawing = false; + this.dirty = false; + + this.textures = null; + this.blendModes = null; + this.shaders = null; + this.sprites = null; + this.shader = null; +};