diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js new file mode 100644 index 0000000..41be102 --- /dev/null +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -0,0 +1,48 @@ +/** + * @class + * @private + */ +function WebGLGraphicsData(gl) { + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +} + +WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; +module.exports = WebGLGraphicsData; + +/** + * + */ +WebGLGraphicsData.prototype.reset = function () { + this.points = []; + this.indices = []; +}; + +/** + * + */ +WebGLGraphicsData.prototype.upload = function () { + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js new file mode 100644 index 0000000..41be102 --- /dev/null +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -0,0 +1,48 @@ +/** + * @class + * @private + */ +function WebGLGraphicsData(gl) { + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +} + +WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; +module.exports = WebGLGraphicsData; + +/** + * + */ +WebGLGraphicsData.prototype.reset = function () { + this.points = []; + this.indices = []; +}; + +/** + * + */ +WebGLGraphicsData.prototype.upload = function () { + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; diff --git a/src/core/index.js b/src/core/index.js index 6f744e9..1d0a398 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -23,12 +23,12 @@ Sprite: require('./sprites/Sprite'), SpriteBatch: require('./sprites/SpriteBatch'), - SpriteRenderer: require('./sprites/SpriteRenderer'), + SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), // primitives - Graphics: require('./primitives/Graphics'), - GraphicsData: require('./primitives/GraphicsData'), - GraphicsRenderer: require('./primitives/GraphicsRenderer'), + Graphics: require('./graphics/Graphics'), + GraphicsData: require('./graphics/GraphicsData'), + GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,10 +42,8 @@ CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), // renderers - webgl - WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), - // WebGLGraphics: require('./renderers/webgl/utils/WebGLGraphics'), - - + WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), + Shader: require('./renderers/webgl/shaders/Shader'), /** * This helper function will automatically detect which renderer you should be using. @@ -103,7 +101,3 @@ return core.autoDetectRenderer(width, height, options, isAndroid); } }; - -// Adding here for now.. sure there is a better place! -core.WebGLRenderer.registerObjectRenderer('sprite', core.SpriteRenderer); -core.WebGLRenderer.registerObjectRenderer('graphics', core.GraphicsRenderer); diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js new file mode 100644 index 0000000..41be102 --- /dev/null +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -0,0 +1,48 @@ +/** + * @class + * @private + */ +function WebGLGraphicsData(gl) { + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +} + +WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; +module.exports = WebGLGraphicsData; + +/** + * + */ +WebGLGraphicsData.prototype.reset = function () { + this.points = []; + this.indices = []; +}; + +/** + * + */ +WebGLGraphicsData.prototype.upload = function () { + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; diff --git a/src/core/index.js b/src/core/index.js index 6f744e9..1d0a398 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -23,12 +23,12 @@ Sprite: require('./sprites/Sprite'), SpriteBatch: require('./sprites/SpriteBatch'), - SpriteRenderer: require('./sprites/SpriteRenderer'), + SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), // primitives - Graphics: require('./primitives/Graphics'), - GraphicsData: require('./primitives/GraphicsData'), - GraphicsRenderer: require('./primitives/GraphicsRenderer'), + Graphics: require('./graphics/Graphics'), + GraphicsData: require('./graphics/GraphicsData'), + GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,10 +42,8 @@ CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), // renderers - webgl - WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), - // WebGLGraphics: require('./renderers/webgl/utils/WebGLGraphics'), - - + WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), + Shader: require('./renderers/webgl/shaders/Shader'), /** * This helper function will automatically detect which renderer you should be using. @@ -103,7 +101,3 @@ return core.autoDetectRenderer(width, height, options, isAndroid); } }; - -// Adding here for now.. sure there is a better place! -core.WebGLRenderer.registerObjectRenderer('sprite', core.SpriteRenderer); -core.WebGLRenderer.registerObjectRenderer('graphics', core.GraphicsRenderer); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index a086950..81de05c 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -268,6 +268,6 @@ matrix.ty = this.ty; return matrix; -} +}; Matrix.IDENTITY = new Matrix(); diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js new file mode 100644 index 0000000..41be102 --- /dev/null +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -0,0 +1,48 @@ +/** + * @class + * @private + */ +function WebGLGraphicsData(gl) { + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +} + +WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; +module.exports = WebGLGraphicsData; + +/** + * + */ +WebGLGraphicsData.prototype.reset = function () { + this.points = []; + this.indices = []; +}; + +/** + * + */ +WebGLGraphicsData.prototype.upload = function () { + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; diff --git a/src/core/index.js b/src/core/index.js index 6f744e9..1d0a398 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -23,12 +23,12 @@ Sprite: require('./sprites/Sprite'), SpriteBatch: require('./sprites/SpriteBatch'), - SpriteRenderer: require('./sprites/SpriteRenderer'), + SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), // primitives - Graphics: require('./primitives/Graphics'), - GraphicsData: require('./primitives/GraphicsData'), - GraphicsRenderer: require('./primitives/GraphicsRenderer'), + Graphics: require('./graphics/Graphics'), + GraphicsData: require('./graphics/GraphicsData'), + GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,10 +42,8 @@ CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), // renderers - webgl - WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), - // WebGLGraphics: require('./renderers/webgl/utils/WebGLGraphics'), - - + WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), + Shader: require('./renderers/webgl/shaders/Shader'), /** * This helper function will automatically detect which renderer you should be using. @@ -103,7 +101,3 @@ return core.autoDetectRenderer(width, height, options, isAndroid); } }; - -// Adding here for now.. sure there is a better place! -core.WebGLRenderer.registerObjectRenderer('sprite', core.SpriteRenderer); -core.WebGLRenderer.registerObjectRenderer('graphics', core.GraphicsRenderer); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index a086950..81de05c 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -268,6 +268,6 @@ matrix.ty = this.ty; return matrix; -} +}; Matrix.IDENTITY = new Matrix(); diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js deleted file mode 100644 index 8db9b45..0000000 --- a/src/core/primitives/Graphics.js +++ /dev/null @@ -1,1116 +0,0 @@ -var DisplayObjectContainer = require('../display/DisplayObjectContainer'), - //Sprite = require('../display/Sprite'), - Texture = require('../textures/Texture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), - // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), - GraphicsData = require('./GraphicsData'), - math = require('../math'), - CONST = require('../const'); - -/** - * The Graphics class contains methods used to draw primitive shapes such as lines, circles and - * rectangles to the display, and color and fill them. - * - * @class - * @extends DisplayObjectContainer - * @namespace PIXI - */ -function Graphics() -{ - DisplayObjectContainer.call(this); - - this.renderable = true; - - /** - * The alpha value used when filling the Graphics object. - * - * @member {number} - * @default 1 - */ - this.fillAlpha = 1; - - /** - * The width (thickness) of any lines drawn. - * - * @member {number} - * @default 0 - */ - this.lineWidth = 0; - - /** - * The color of any lines drawn. - * - * @member {string} - * @default 0 - */ - this.lineColor = 0; - - /** - * Graphics data - * - * @member {GraphicsData[]} - * @private - */ - this.graphicsData = []; - - /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * Current path - * - * @member {GraphicsData} - * @private - */ - this.currentPath = null; - - /** - * Array containing some WebGL-related properties used by the WebGL renderer. - * - * @member {object} - * @private - */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; - - /** - * Whether this shape is being used as a mask. - * - * @member {boolean} - */ - this.isMask = false; - - /** - * The bounds' padding used for bounds calculation. - * - * @member {number} - */ - this.boundsPadding = 0; - - /** - * A cache of the local bounds to prevent recalculation. - * - * @member {Rectangle} - * @private - */ - this._localBounds = new math.Rectangle(0,0,1,1); - - /** - * Used to detect if the graphics object has changed. If this is set to true then the graphics - * object will be recalculated. - * - * @member {boolean} - * @private - */ - this.dirty = true; - - /** - * Used to detect if the WebGL graphics object has changed. If this is set to true then the - * graphics object will be recalculated. - * - * @member {boolean} - * @private - */ - this.glDirty = false; - - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; -} - -// constructor -Graphics.prototype = Object.create(DisplayObjectContainer.prototype); -Graphics.prototype.constructor = Graphics; -module.exports = Graphics; - -Object.defineProperties(Graphics.prototype, { - /** - * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. - * This is useful if your graphics element does not change often, as it will speed up the rendering - * of the object in exchange for taking up texture memory. It is also useful if you need the graphics - * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if - * you are constantly redrawing the graphics element. - * - * @member {boolean} - * @memberof Graphics# - * @default false - * @private - */ - cacheAsBitmap: { - get: function () - { - return this._cacheAsBitmap; - }, - set: function (value) - { - this._cacheAsBitmap = value; - - if (this._cacheAsBitmap) - { - this._generateCachedSprite(); - } - else - { - this.destroyCachedSprite(); - this.dirty = true; - } - } - } -}); - -/** - * Creates a new Graphics object with the same values as this one. - * - * @return {Graphics} - */ -GraphicsData.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = this.dirty; - clone.glDirty = this.glDirty; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData.clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {Graphics} - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (arguments.length < 3) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); - } - else - { - // otherwise its empty so lets just set the line properties - this.currentPath.lineWidth = this.lineWidth; - this.currentPath.lineColor = this.lineColor; - this.currentPath.lineAlpha = this.lineAlpha; - } - } - - return this; -}; - -/** - * Moves the current drawing position to x, y. - * - * @param x {number} the X coordinate to move to - * @param y {number} the Y coordinate to move to - * @return {Graphics} - */ -Graphics.prototype.moveTo = function (x, y) -{ - this.drawShape(new math.Polygon([x,y])); - - return this; -}; - -/** - * Draws a line using the current line style from the current drawing position to (x, y); - * The current drawing position is then set to (x, y). - * - * @param x {number} the X coordinate to draw to - * @param y {number} the Y coordinate to draw to - * @return {Graphics} - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a quadratic bezier curve and then draws it. - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @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 {Graphics} - */ -Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var xa, - ya, - n = 20, - points = this.currentPath.shape.points; - - if (points.length === 0) - { - this.moveTo(0, 0); - } - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - for (var i = 1; i <= n; ++i) - { - j = i / n; - - xa = fromX + ( (cpX - fromX) * j ); - ya = fromY + ( (cpY - fromY) * j ); - - points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), - ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); - } - - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a bezier curve and then draws it. - * - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param cpX2 {number} Second Control point x - * @param cpY2 {number} Second Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {Graphics} - */ -Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var n = 20, - dt, - dt2, - dt3, - t2, - t3, - points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - - for (var i = 1; i <= n; ++i) - { - j = i / n; - - dt = (1 - j); - dt2 = dt * dt; - dt3 = dt2 * dt; - - t2 = j * j; - t3 = t2 * j; - - points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, - dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); - } - - this.dirty = true; - - return this; -}; - -/** - * The arcTo() method creates an arc/curve between two tangents on the canvas. - * - * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! - * - * @param x1 {number} The x-coordinate of the beginning of the arc - * @param y1 {number} The y-coordinate of the beginning of the arc - * @param x2 {number} The x-coordinate of the end of the arc - * @param y2 {number} The y-coordinate of the end of the arc - * @param radius {number} The radius of the arc - * @return {Graphics} - */ -Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points.push(x1, y1); - } - } - else - { - this.moveTo(x1, y1); - } - - var points = this.currentPath.shape.points, - fromX = points[points.length-2], - fromY = points[points.length-1], - a1 = fromY - y1, - b1 = fromX - x1, - a2 = y2 - y1, - b2 = x2 - x1, - mm = Math.abs(a1 * b2 - b1 * a2); - - if (mm < 1.0e-8 || radius === 0) - { - if (points[points.length-2] !== x1 || points[points.length-1] !== y1) - { - points.push(x1, y1); - } - } - else - { - var dd = a1 * a1 + b1 * b1, - cc = a2 * a2 + b2 * b2, - tt = a1 * a2 + b1 * b2, - k1 = radius * Math.sqrt(dd) / mm, - k2 = radius * Math.sqrt(cc) / mm, - j1 = k1 * tt / dd, - j2 = k2 * tt / cc, - cx = k1 * b2 + k2 * b1, - cy = k1 * a2 + k2 * a1, - px = b1 * (k2 + j1), - py = a1 * (k2 + j1), - qx = b2 * (k1 + j2), - qy = a2 * (k1 + j2), - startAngle = Math.atan2(py - cy, px - cx), - endAngle = Math.atan2(qy - cy, qx - cx); - - this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); - } - - this.dirty = true; - - return this; -}; - -/** - * The arc method creates an arc/curve (used to create circles, or parts of circles). - * - * @param cx {number} The x-coordinate of the center of the circle - * @param cy {number} The y-coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) - * @param endAngle {number} The ending angle, in radians - * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {Graphics} - */ -Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - var points; - - // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... - if (this.currentPath) - { - points = this.currentPath.shape.points; - - if (points.length === 0) - { - points.push(startX, startY); - } - else if (points[points.length-2] !== startX || points[points.length-1] !== startY) - { - points.push(startX, startY); - } - } - else - { - this.moveTo(startX, startY); - points = this.currentPath.shape.points; - } - - if (startAngle === endAngle) - { - return this; - } - - if (!anticlockwise && endAngle <= startAngle) - { - endAngle += Math.PI * 2; - } - else if (anticlockwise && startAngle <= endAngle) - { - startAngle += Math.PI * 2; - } - - var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); - var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; - - if (sweep === 0) - { - return this; - } - - var theta = sweep/(segs*2); - var theta2 = theta*2; - - var cTheta = Math.cos(theta); - var sTheta = Math.sin(theta); - - var segMinus = segs - 1; - - var remainder = ( segMinus % 1 ) / segMinus; - - for (var i = 0; i <= segMinus; ++i) - { - var real = i + remainder * i; - var angle = ((theta) + startAngle + (theta2 * real)); - - var c = Math.cos(angle); - var s = -Math.sin(angle); - - points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, - ( (cTheta * -s) + (sTheta * c) ) * radius + cy); - } - - this.dirty = true; - - return this; -}; - -/** - * Specifies a simple one-color fill that subsequent calls to other Graphics methods - * (such as lineTo() or drawCircle()) use when drawing. - * - * @param color {number} the color of the fill - * @param alpha {number} the alpha of the fill - * @return {Graphics} - */ -Graphics.prototype.beginFill = function (color, alpha) -{ - this.filling = true; - this.fillColor = color || 0; - this.fillAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length <= 2) - { - this.currentPath.fill = this.filling; - this.currentPath.fillColor = this.fillColor; - this.currentPath.fillAlpha = this.fillAlpha; - } - } - return this; -}; - -/** - * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. - * - * @return {Graphics} - */ -Graphics.prototype.endFill = function () -{ - this.filling = false; - this.fillColor = null; - this.fillAlpha = 1; - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @return {Graphics} - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {Graphics} - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {Graphics} - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {Array} The path data used to construct the polygon. - * @return {Graphics} - */ -Graphics.prototype.drawPolygon = function (path) -{ - if (!(path instanceof Array)) - { - path = Array.prototype.slice.call(arguments); - } - - this.drawShape(new math.Polygon(path)); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {Graphics} - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty = true; - this.clearDirty = true; - this.graphicsData = []; - - return this; -}; - -/** - * Useful function that returns a texture of the graphics object that can then be used to create sprites - * This can be quite useful if your geometry is complicated and needs to be reused multiple times. - * - * @param resolution {number} The resolution of the texture being generated - * @param scaleMode {number} Should be one of the scaleMode consts - * @return {Texture} a texture of the graphics object - */ -Graphics.prototype.generateTexture = function (resolution, scaleMode) -{ - resolution = resolution || 1; - - var bounds = this.getBounds(); - - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); - - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - canvasBuffer.context.scale(resolution, resolution); - - canvasBuffer.context.translate(-bounds.x,-bounds.y); - - CanvasGraphics.renderGraphics(this, canvasBuffer.context); - - return texture; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (this.isMask === true) - { - return; - } - - // this code may still be needed so leaving for now.. - // - /* - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture on the gpu too! - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.worldAlpha = this.worldAlpha; - - Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); - - return; - } - - */ - - if (this.glDirty) - { - this.dirty = true; - this.glDirty = false; - } - - renderer.setObjectRenderer(renderer.objectRenderers.graphics); - renderer.objectRenderers.graphics.render(this); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} - * @private - */ -Graphics.prototype.renderCanvas = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.alpha <= 0 || this.isMask === true) - { - return; - } - - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.alpha = this.alpha; - - Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); - - return; - } - else - { - var context = renderer.context; - var transform = this.worldTransform; - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - var resolution = renderer.resolution; - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } - } -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @return {Rectangle} the rectangular bounding area - */ -Graphics.prototype.getBounds = function (matrix) -{ - // return an empty object if the item is a mask! - if (this.isMask) - { - return math.Rectangle.EMPTY; - } - - if (this.dirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.dirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - return this._bounds; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; - - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; -}; - -/** - * Generates the cached sprite when the sprite has cacheAsBitmap = true - * - * @private - */ -Graphics.prototype._generateCachedSprite = function () -{ - var bounds = this.getLocalBounds(); - - if (!this._cachedSprite) - { - var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); - var texture = Texture.fromCanvas(canvasBuffer.canvas); - - this._cachedSprite = new Sprite(texture); - this._cachedSprite.buffer = canvasBuffer; - - this._cachedSprite.worldTransform = this.worldTransform; - } - else - { - this._cachedSprite.buffer.resize(bounds.width, bounds.height); - } - - // leverage the anchor to account for the offset of the element - this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); - this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); - - // this._cachedSprite.buffer.context.save(); - this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); - - // make sure we set the alpha of the graphics to 1 for the render.. - this.worldAlpha = 1; - - // now render the graphic.. - CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); - - this._cachedSprite.alpha = this.alpha; -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Graphics.prototype.updateCachedSpriteTexture = function () -{ - var cachedSprite = this._cachedSprite; - var texture = cachedSprite.texture; - var canvas = cachedSprite.buffer.canvas; - - texture.baseTexture.width = canvas.width; - texture.baseTexture.height = canvas.height; - texture.crop.width = texture.frame.width = canvas.width; - texture.crop.height = texture.frame.height = canvas.height; - - cachedSprite._width = canvas.width; - cachedSprite._height = canvas.height; - - // update the dirty base textures - texture.baseTexture.dirty(); -}; - -/** - * Destroys a previous cached sprite. - * - */ -Graphics.prototype.destroyCachedSprite = function () -{ - this._cachedSprite.texture.destroy(true); - - // let the gc collect the unused sprite - // TODO could be object pooled! - this._cachedSprite = null; -}; - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. - * @return {GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = this.filling; - this.currentPath = data; - } - - this.dirty = true; - - return data; -}; diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js new file mode 100644 index 0000000..41be102 --- /dev/null +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -0,0 +1,48 @@ +/** + * @class + * @private + */ +function WebGLGraphicsData(gl) { + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +} + +WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; +module.exports = WebGLGraphicsData; + +/** + * + */ +WebGLGraphicsData.prototype.reset = function () { + this.points = []; + this.indices = []; +}; + +/** + * + */ +WebGLGraphicsData.prototype.upload = function () { + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; diff --git a/src/core/index.js b/src/core/index.js index 6f744e9..1d0a398 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -23,12 +23,12 @@ Sprite: require('./sprites/Sprite'), SpriteBatch: require('./sprites/SpriteBatch'), - SpriteRenderer: require('./sprites/SpriteRenderer'), + SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), // primitives - Graphics: require('./primitives/Graphics'), - GraphicsData: require('./primitives/GraphicsData'), - GraphicsRenderer: require('./primitives/GraphicsRenderer'), + Graphics: require('./graphics/Graphics'), + GraphicsData: require('./graphics/GraphicsData'), + GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,10 +42,8 @@ CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), // renderers - webgl - WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), - // WebGLGraphics: require('./renderers/webgl/utils/WebGLGraphics'), - - + WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), + Shader: require('./renderers/webgl/shaders/Shader'), /** * This helper function will automatically detect which renderer you should be using. @@ -103,7 +101,3 @@ return core.autoDetectRenderer(width, height, options, isAndroid); } }; - -// Adding here for now.. sure there is a better place! -core.WebGLRenderer.registerObjectRenderer('sprite', core.SpriteRenderer); -core.WebGLRenderer.registerObjectRenderer('graphics', core.GraphicsRenderer); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index a086950..81de05c 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -268,6 +268,6 @@ matrix.ty = this.ty; return matrix; -} +}; Matrix.IDENTITY = new Matrix(); diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js deleted file mode 100644 index 8db9b45..0000000 --- a/src/core/primitives/Graphics.js +++ /dev/null @@ -1,1116 +0,0 @@ -var DisplayObjectContainer = require('../display/DisplayObjectContainer'), - //Sprite = require('../display/Sprite'), - Texture = require('../textures/Texture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), - // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), - GraphicsData = require('./GraphicsData'), - math = require('../math'), - CONST = require('../const'); - -/** - * The Graphics class contains methods used to draw primitive shapes such as lines, circles and - * rectangles to the display, and color and fill them. - * - * @class - * @extends DisplayObjectContainer - * @namespace PIXI - */ -function Graphics() -{ - DisplayObjectContainer.call(this); - - this.renderable = true; - - /** - * The alpha value used when filling the Graphics object. - * - * @member {number} - * @default 1 - */ - this.fillAlpha = 1; - - /** - * The width (thickness) of any lines drawn. - * - * @member {number} - * @default 0 - */ - this.lineWidth = 0; - - /** - * The color of any lines drawn. - * - * @member {string} - * @default 0 - */ - this.lineColor = 0; - - /** - * Graphics data - * - * @member {GraphicsData[]} - * @private - */ - this.graphicsData = []; - - /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * Current path - * - * @member {GraphicsData} - * @private - */ - this.currentPath = null; - - /** - * Array containing some WebGL-related properties used by the WebGL renderer. - * - * @member {object} - * @private - */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; - - /** - * Whether this shape is being used as a mask. - * - * @member {boolean} - */ - this.isMask = false; - - /** - * The bounds' padding used for bounds calculation. - * - * @member {number} - */ - this.boundsPadding = 0; - - /** - * A cache of the local bounds to prevent recalculation. - * - * @member {Rectangle} - * @private - */ - this._localBounds = new math.Rectangle(0,0,1,1); - - /** - * Used to detect if the graphics object has changed. If this is set to true then the graphics - * object will be recalculated. - * - * @member {boolean} - * @private - */ - this.dirty = true; - - /** - * Used to detect if the WebGL graphics object has changed. If this is set to true then the - * graphics object will be recalculated. - * - * @member {boolean} - * @private - */ - this.glDirty = false; - - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; -} - -// constructor -Graphics.prototype = Object.create(DisplayObjectContainer.prototype); -Graphics.prototype.constructor = Graphics; -module.exports = Graphics; - -Object.defineProperties(Graphics.prototype, { - /** - * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. - * This is useful if your graphics element does not change often, as it will speed up the rendering - * of the object in exchange for taking up texture memory. It is also useful if you need the graphics - * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if - * you are constantly redrawing the graphics element. - * - * @member {boolean} - * @memberof Graphics# - * @default false - * @private - */ - cacheAsBitmap: { - get: function () - { - return this._cacheAsBitmap; - }, - set: function (value) - { - this._cacheAsBitmap = value; - - if (this._cacheAsBitmap) - { - this._generateCachedSprite(); - } - else - { - this.destroyCachedSprite(); - this.dirty = true; - } - } - } -}); - -/** - * Creates a new Graphics object with the same values as this one. - * - * @return {Graphics} - */ -GraphicsData.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = this.dirty; - clone.glDirty = this.glDirty; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData.clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {Graphics} - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (arguments.length < 3) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); - } - else - { - // otherwise its empty so lets just set the line properties - this.currentPath.lineWidth = this.lineWidth; - this.currentPath.lineColor = this.lineColor; - this.currentPath.lineAlpha = this.lineAlpha; - } - } - - return this; -}; - -/** - * Moves the current drawing position to x, y. - * - * @param x {number} the X coordinate to move to - * @param y {number} the Y coordinate to move to - * @return {Graphics} - */ -Graphics.prototype.moveTo = function (x, y) -{ - this.drawShape(new math.Polygon([x,y])); - - return this; -}; - -/** - * Draws a line using the current line style from the current drawing position to (x, y); - * The current drawing position is then set to (x, y). - * - * @param x {number} the X coordinate to draw to - * @param y {number} the Y coordinate to draw to - * @return {Graphics} - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a quadratic bezier curve and then draws it. - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @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 {Graphics} - */ -Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var xa, - ya, - n = 20, - points = this.currentPath.shape.points; - - if (points.length === 0) - { - this.moveTo(0, 0); - } - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - for (var i = 1; i <= n; ++i) - { - j = i / n; - - xa = fromX + ( (cpX - fromX) * j ); - ya = fromY + ( (cpY - fromY) * j ); - - points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), - ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); - } - - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a bezier curve and then draws it. - * - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param cpX2 {number} Second Control point x - * @param cpY2 {number} Second Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {Graphics} - */ -Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var n = 20, - dt, - dt2, - dt3, - t2, - t3, - points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - - for (var i = 1; i <= n; ++i) - { - j = i / n; - - dt = (1 - j); - dt2 = dt * dt; - dt3 = dt2 * dt; - - t2 = j * j; - t3 = t2 * j; - - points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, - dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); - } - - this.dirty = true; - - return this; -}; - -/** - * The arcTo() method creates an arc/curve between two tangents on the canvas. - * - * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! - * - * @param x1 {number} The x-coordinate of the beginning of the arc - * @param y1 {number} The y-coordinate of the beginning of the arc - * @param x2 {number} The x-coordinate of the end of the arc - * @param y2 {number} The y-coordinate of the end of the arc - * @param radius {number} The radius of the arc - * @return {Graphics} - */ -Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points.push(x1, y1); - } - } - else - { - this.moveTo(x1, y1); - } - - var points = this.currentPath.shape.points, - fromX = points[points.length-2], - fromY = points[points.length-1], - a1 = fromY - y1, - b1 = fromX - x1, - a2 = y2 - y1, - b2 = x2 - x1, - mm = Math.abs(a1 * b2 - b1 * a2); - - if (mm < 1.0e-8 || radius === 0) - { - if (points[points.length-2] !== x1 || points[points.length-1] !== y1) - { - points.push(x1, y1); - } - } - else - { - var dd = a1 * a1 + b1 * b1, - cc = a2 * a2 + b2 * b2, - tt = a1 * a2 + b1 * b2, - k1 = radius * Math.sqrt(dd) / mm, - k2 = radius * Math.sqrt(cc) / mm, - j1 = k1 * tt / dd, - j2 = k2 * tt / cc, - cx = k1 * b2 + k2 * b1, - cy = k1 * a2 + k2 * a1, - px = b1 * (k2 + j1), - py = a1 * (k2 + j1), - qx = b2 * (k1 + j2), - qy = a2 * (k1 + j2), - startAngle = Math.atan2(py - cy, px - cx), - endAngle = Math.atan2(qy - cy, qx - cx); - - this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); - } - - this.dirty = true; - - return this; -}; - -/** - * The arc method creates an arc/curve (used to create circles, or parts of circles). - * - * @param cx {number} The x-coordinate of the center of the circle - * @param cy {number} The y-coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) - * @param endAngle {number} The ending angle, in radians - * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {Graphics} - */ -Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - var points; - - // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... - if (this.currentPath) - { - points = this.currentPath.shape.points; - - if (points.length === 0) - { - points.push(startX, startY); - } - else if (points[points.length-2] !== startX || points[points.length-1] !== startY) - { - points.push(startX, startY); - } - } - else - { - this.moveTo(startX, startY); - points = this.currentPath.shape.points; - } - - if (startAngle === endAngle) - { - return this; - } - - if (!anticlockwise && endAngle <= startAngle) - { - endAngle += Math.PI * 2; - } - else if (anticlockwise && startAngle <= endAngle) - { - startAngle += Math.PI * 2; - } - - var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); - var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; - - if (sweep === 0) - { - return this; - } - - var theta = sweep/(segs*2); - var theta2 = theta*2; - - var cTheta = Math.cos(theta); - var sTheta = Math.sin(theta); - - var segMinus = segs - 1; - - var remainder = ( segMinus % 1 ) / segMinus; - - for (var i = 0; i <= segMinus; ++i) - { - var real = i + remainder * i; - var angle = ((theta) + startAngle + (theta2 * real)); - - var c = Math.cos(angle); - var s = -Math.sin(angle); - - points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, - ( (cTheta * -s) + (sTheta * c) ) * radius + cy); - } - - this.dirty = true; - - return this; -}; - -/** - * Specifies a simple one-color fill that subsequent calls to other Graphics methods - * (such as lineTo() or drawCircle()) use when drawing. - * - * @param color {number} the color of the fill - * @param alpha {number} the alpha of the fill - * @return {Graphics} - */ -Graphics.prototype.beginFill = function (color, alpha) -{ - this.filling = true; - this.fillColor = color || 0; - this.fillAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length <= 2) - { - this.currentPath.fill = this.filling; - this.currentPath.fillColor = this.fillColor; - this.currentPath.fillAlpha = this.fillAlpha; - } - } - return this; -}; - -/** - * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. - * - * @return {Graphics} - */ -Graphics.prototype.endFill = function () -{ - this.filling = false; - this.fillColor = null; - this.fillAlpha = 1; - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @return {Graphics} - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {Graphics} - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {Graphics} - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {Array} The path data used to construct the polygon. - * @return {Graphics} - */ -Graphics.prototype.drawPolygon = function (path) -{ - if (!(path instanceof Array)) - { - path = Array.prototype.slice.call(arguments); - } - - this.drawShape(new math.Polygon(path)); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {Graphics} - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty = true; - this.clearDirty = true; - this.graphicsData = []; - - return this; -}; - -/** - * Useful function that returns a texture of the graphics object that can then be used to create sprites - * This can be quite useful if your geometry is complicated and needs to be reused multiple times. - * - * @param resolution {number} The resolution of the texture being generated - * @param scaleMode {number} Should be one of the scaleMode consts - * @return {Texture} a texture of the graphics object - */ -Graphics.prototype.generateTexture = function (resolution, scaleMode) -{ - resolution = resolution || 1; - - var bounds = this.getBounds(); - - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); - - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - canvasBuffer.context.scale(resolution, resolution); - - canvasBuffer.context.translate(-bounds.x,-bounds.y); - - CanvasGraphics.renderGraphics(this, canvasBuffer.context); - - return texture; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (this.isMask === true) - { - return; - } - - // this code may still be needed so leaving for now.. - // - /* - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture on the gpu too! - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.worldAlpha = this.worldAlpha; - - Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); - - return; - } - - */ - - if (this.glDirty) - { - this.dirty = true; - this.glDirty = false; - } - - renderer.setObjectRenderer(renderer.objectRenderers.graphics); - renderer.objectRenderers.graphics.render(this); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} - * @private - */ -Graphics.prototype.renderCanvas = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.alpha <= 0 || this.isMask === true) - { - return; - } - - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.alpha = this.alpha; - - Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); - - return; - } - else - { - var context = renderer.context; - var transform = this.worldTransform; - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - var resolution = renderer.resolution; - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } - } -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @return {Rectangle} the rectangular bounding area - */ -Graphics.prototype.getBounds = function (matrix) -{ - // return an empty object if the item is a mask! - if (this.isMask) - { - return math.Rectangle.EMPTY; - } - - if (this.dirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.dirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - return this._bounds; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; - - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; -}; - -/** - * Generates the cached sprite when the sprite has cacheAsBitmap = true - * - * @private - */ -Graphics.prototype._generateCachedSprite = function () -{ - var bounds = this.getLocalBounds(); - - if (!this._cachedSprite) - { - var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); - var texture = Texture.fromCanvas(canvasBuffer.canvas); - - this._cachedSprite = new Sprite(texture); - this._cachedSprite.buffer = canvasBuffer; - - this._cachedSprite.worldTransform = this.worldTransform; - } - else - { - this._cachedSprite.buffer.resize(bounds.width, bounds.height); - } - - // leverage the anchor to account for the offset of the element - this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); - this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); - - // this._cachedSprite.buffer.context.save(); - this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); - - // make sure we set the alpha of the graphics to 1 for the render.. - this.worldAlpha = 1; - - // now render the graphic.. - CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); - - this._cachedSprite.alpha = this.alpha; -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Graphics.prototype.updateCachedSpriteTexture = function () -{ - var cachedSprite = this._cachedSprite; - var texture = cachedSprite.texture; - var canvas = cachedSprite.buffer.canvas; - - texture.baseTexture.width = canvas.width; - texture.baseTexture.height = canvas.height; - texture.crop.width = texture.frame.width = canvas.width; - texture.crop.height = texture.frame.height = canvas.height; - - cachedSprite._width = canvas.width; - cachedSprite._height = canvas.height; - - // update the dirty base textures - texture.baseTexture.dirty(); -}; - -/** - * Destroys a previous cached sprite. - * - */ -Graphics.prototype.destroyCachedSprite = function () -{ - this._cachedSprite.texture.destroy(true); - - // let the gc collect the unused sprite - // TODO could be object pooled! - this._cachedSprite = null; -}; - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. - * @return {GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = this.filling; - this.currentPath = data; - } - - this.dirty = true; - - return data; -}; diff --git a/src/core/primitives/GraphicsData.js b/src/core/primitives/GraphicsData.js deleted file mode 100644 index 788f6f2..0000000 --- a/src/core/primitives/GraphicsData.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * A GraphicsData object. - * - * @class - * @namespace PIXI - */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - this.lineWidth = lineWidth; - this.lineColor = lineColor; - this.lineAlpha = lineAlpha; - this._lineTint = lineColor; - - this.fillColor = fillColor; - this.fillAlpha = fillAlpha; - this._fillTint = fillColor; - this.fill = fill; - - this.shape = shape; - this.type = shape.type; -} - -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {GraphicsData} - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js new file mode 100644 index 0000000..41be102 --- /dev/null +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -0,0 +1,48 @@ +/** + * @class + * @private + */ +function WebGLGraphicsData(gl) { + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +} + +WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; +module.exports = WebGLGraphicsData; + +/** + * + */ +WebGLGraphicsData.prototype.reset = function () { + this.points = []; + this.indices = []; +}; + +/** + * + */ +WebGLGraphicsData.prototype.upload = function () { + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; diff --git a/src/core/index.js b/src/core/index.js index 6f744e9..1d0a398 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -23,12 +23,12 @@ Sprite: require('./sprites/Sprite'), SpriteBatch: require('./sprites/SpriteBatch'), - SpriteRenderer: require('./sprites/SpriteRenderer'), + SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), // primitives - Graphics: require('./primitives/Graphics'), - GraphicsData: require('./primitives/GraphicsData'), - GraphicsRenderer: require('./primitives/GraphicsRenderer'), + Graphics: require('./graphics/Graphics'), + GraphicsData: require('./graphics/GraphicsData'), + GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,10 +42,8 @@ CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), // renderers - webgl - WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), - // WebGLGraphics: require('./renderers/webgl/utils/WebGLGraphics'), - - + WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), + Shader: require('./renderers/webgl/shaders/Shader'), /** * This helper function will automatically detect which renderer you should be using. @@ -103,7 +101,3 @@ return core.autoDetectRenderer(width, height, options, isAndroid); } }; - -// Adding here for now.. sure there is a better place! -core.WebGLRenderer.registerObjectRenderer('sprite', core.SpriteRenderer); -core.WebGLRenderer.registerObjectRenderer('graphics', core.GraphicsRenderer); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index a086950..81de05c 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -268,6 +268,6 @@ matrix.ty = this.ty; return matrix; -} +}; Matrix.IDENTITY = new Matrix(); diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js deleted file mode 100644 index 8db9b45..0000000 --- a/src/core/primitives/Graphics.js +++ /dev/null @@ -1,1116 +0,0 @@ -var DisplayObjectContainer = require('../display/DisplayObjectContainer'), - //Sprite = require('../display/Sprite'), - Texture = require('../textures/Texture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), - // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), - GraphicsData = require('./GraphicsData'), - math = require('../math'), - CONST = require('../const'); - -/** - * The Graphics class contains methods used to draw primitive shapes such as lines, circles and - * rectangles to the display, and color and fill them. - * - * @class - * @extends DisplayObjectContainer - * @namespace PIXI - */ -function Graphics() -{ - DisplayObjectContainer.call(this); - - this.renderable = true; - - /** - * The alpha value used when filling the Graphics object. - * - * @member {number} - * @default 1 - */ - this.fillAlpha = 1; - - /** - * The width (thickness) of any lines drawn. - * - * @member {number} - * @default 0 - */ - this.lineWidth = 0; - - /** - * The color of any lines drawn. - * - * @member {string} - * @default 0 - */ - this.lineColor = 0; - - /** - * Graphics data - * - * @member {GraphicsData[]} - * @private - */ - this.graphicsData = []; - - /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * Current path - * - * @member {GraphicsData} - * @private - */ - this.currentPath = null; - - /** - * Array containing some WebGL-related properties used by the WebGL renderer. - * - * @member {object} - * @private - */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; - - /** - * Whether this shape is being used as a mask. - * - * @member {boolean} - */ - this.isMask = false; - - /** - * The bounds' padding used for bounds calculation. - * - * @member {number} - */ - this.boundsPadding = 0; - - /** - * A cache of the local bounds to prevent recalculation. - * - * @member {Rectangle} - * @private - */ - this._localBounds = new math.Rectangle(0,0,1,1); - - /** - * Used to detect if the graphics object has changed. If this is set to true then the graphics - * object will be recalculated. - * - * @member {boolean} - * @private - */ - this.dirty = true; - - /** - * Used to detect if the WebGL graphics object has changed. If this is set to true then the - * graphics object will be recalculated. - * - * @member {boolean} - * @private - */ - this.glDirty = false; - - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; -} - -// constructor -Graphics.prototype = Object.create(DisplayObjectContainer.prototype); -Graphics.prototype.constructor = Graphics; -module.exports = Graphics; - -Object.defineProperties(Graphics.prototype, { - /** - * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. - * This is useful if your graphics element does not change often, as it will speed up the rendering - * of the object in exchange for taking up texture memory. It is also useful if you need the graphics - * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if - * you are constantly redrawing the graphics element. - * - * @member {boolean} - * @memberof Graphics# - * @default false - * @private - */ - cacheAsBitmap: { - get: function () - { - return this._cacheAsBitmap; - }, - set: function (value) - { - this._cacheAsBitmap = value; - - if (this._cacheAsBitmap) - { - this._generateCachedSprite(); - } - else - { - this.destroyCachedSprite(); - this.dirty = true; - } - } - } -}); - -/** - * Creates a new Graphics object with the same values as this one. - * - * @return {Graphics} - */ -GraphicsData.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = this.dirty; - clone.glDirty = this.glDirty; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData.clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {Graphics} - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (arguments.length < 3) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); - } - else - { - // otherwise its empty so lets just set the line properties - this.currentPath.lineWidth = this.lineWidth; - this.currentPath.lineColor = this.lineColor; - this.currentPath.lineAlpha = this.lineAlpha; - } - } - - return this; -}; - -/** - * Moves the current drawing position to x, y. - * - * @param x {number} the X coordinate to move to - * @param y {number} the Y coordinate to move to - * @return {Graphics} - */ -Graphics.prototype.moveTo = function (x, y) -{ - this.drawShape(new math.Polygon([x,y])); - - return this; -}; - -/** - * Draws a line using the current line style from the current drawing position to (x, y); - * The current drawing position is then set to (x, y). - * - * @param x {number} the X coordinate to draw to - * @param y {number} the Y coordinate to draw to - * @return {Graphics} - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a quadratic bezier curve and then draws it. - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @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 {Graphics} - */ -Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var xa, - ya, - n = 20, - points = this.currentPath.shape.points; - - if (points.length === 0) - { - this.moveTo(0, 0); - } - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - for (var i = 1; i <= n; ++i) - { - j = i / n; - - xa = fromX + ( (cpX - fromX) * j ); - ya = fromY + ( (cpY - fromY) * j ); - - points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), - ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); - } - - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a bezier curve and then draws it. - * - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param cpX2 {number} Second Control point x - * @param cpY2 {number} Second Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {Graphics} - */ -Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var n = 20, - dt, - dt2, - dt3, - t2, - t3, - points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - - for (var i = 1; i <= n; ++i) - { - j = i / n; - - dt = (1 - j); - dt2 = dt * dt; - dt3 = dt2 * dt; - - t2 = j * j; - t3 = t2 * j; - - points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, - dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); - } - - this.dirty = true; - - return this; -}; - -/** - * The arcTo() method creates an arc/curve between two tangents on the canvas. - * - * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! - * - * @param x1 {number} The x-coordinate of the beginning of the arc - * @param y1 {number} The y-coordinate of the beginning of the arc - * @param x2 {number} The x-coordinate of the end of the arc - * @param y2 {number} The y-coordinate of the end of the arc - * @param radius {number} The radius of the arc - * @return {Graphics} - */ -Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points.push(x1, y1); - } - } - else - { - this.moveTo(x1, y1); - } - - var points = this.currentPath.shape.points, - fromX = points[points.length-2], - fromY = points[points.length-1], - a1 = fromY - y1, - b1 = fromX - x1, - a2 = y2 - y1, - b2 = x2 - x1, - mm = Math.abs(a1 * b2 - b1 * a2); - - if (mm < 1.0e-8 || radius === 0) - { - if (points[points.length-2] !== x1 || points[points.length-1] !== y1) - { - points.push(x1, y1); - } - } - else - { - var dd = a1 * a1 + b1 * b1, - cc = a2 * a2 + b2 * b2, - tt = a1 * a2 + b1 * b2, - k1 = radius * Math.sqrt(dd) / mm, - k2 = radius * Math.sqrt(cc) / mm, - j1 = k1 * tt / dd, - j2 = k2 * tt / cc, - cx = k1 * b2 + k2 * b1, - cy = k1 * a2 + k2 * a1, - px = b1 * (k2 + j1), - py = a1 * (k2 + j1), - qx = b2 * (k1 + j2), - qy = a2 * (k1 + j2), - startAngle = Math.atan2(py - cy, px - cx), - endAngle = Math.atan2(qy - cy, qx - cx); - - this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); - } - - this.dirty = true; - - return this; -}; - -/** - * The arc method creates an arc/curve (used to create circles, or parts of circles). - * - * @param cx {number} The x-coordinate of the center of the circle - * @param cy {number} The y-coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) - * @param endAngle {number} The ending angle, in radians - * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {Graphics} - */ -Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - var points; - - // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... - if (this.currentPath) - { - points = this.currentPath.shape.points; - - if (points.length === 0) - { - points.push(startX, startY); - } - else if (points[points.length-2] !== startX || points[points.length-1] !== startY) - { - points.push(startX, startY); - } - } - else - { - this.moveTo(startX, startY); - points = this.currentPath.shape.points; - } - - if (startAngle === endAngle) - { - return this; - } - - if (!anticlockwise && endAngle <= startAngle) - { - endAngle += Math.PI * 2; - } - else if (anticlockwise && startAngle <= endAngle) - { - startAngle += Math.PI * 2; - } - - var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); - var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; - - if (sweep === 0) - { - return this; - } - - var theta = sweep/(segs*2); - var theta2 = theta*2; - - var cTheta = Math.cos(theta); - var sTheta = Math.sin(theta); - - var segMinus = segs - 1; - - var remainder = ( segMinus % 1 ) / segMinus; - - for (var i = 0; i <= segMinus; ++i) - { - var real = i + remainder * i; - var angle = ((theta) + startAngle + (theta2 * real)); - - var c = Math.cos(angle); - var s = -Math.sin(angle); - - points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, - ( (cTheta * -s) + (sTheta * c) ) * radius + cy); - } - - this.dirty = true; - - return this; -}; - -/** - * Specifies a simple one-color fill that subsequent calls to other Graphics methods - * (such as lineTo() or drawCircle()) use when drawing. - * - * @param color {number} the color of the fill - * @param alpha {number} the alpha of the fill - * @return {Graphics} - */ -Graphics.prototype.beginFill = function (color, alpha) -{ - this.filling = true; - this.fillColor = color || 0; - this.fillAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length <= 2) - { - this.currentPath.fill = this.filling; - this.currentPath.fillColor = this.fillColor; - this.currentPath.fillAlpha = this.fillAlpha; - } - } - return this; -}; - -/** - * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. - * - * @return {Graphics} - */ -Graphics.prototype.endFill = function () -{ - this.filling = false; - this.fillColor = null; - this.fillAlpha = 1; - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @return {Graphics} - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {Graphics} - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {Graphics} - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {Array} The path data used to construct the polygon. - * @return {Graphics} - */ -Graphics.prototype.drawPolygon = function (path) -{ - if (!(path instanceof Array)) - { - path = Array.prototype.slice.call(arguments); - } - - this.drawShape(new math.Polygon(path)); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {Graphics} - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty = true; - this.clearDirty = true; - this.graphicsData = []; - - return this; -}; - -/** - * Useful function that returns a texture of the graphics object that can then be used to create sprites - * This can be quite useful if your geometry is complicated and needs to be reused multiple times. - * - * @param resolution {number} The resolution of the texture being generated - * @param scaleMode {number} Should be one of the scaleMode consts - * @return {Texture} a texture of the graphics object - */ -Graphics.prototype.generateTexture = function (resolution, scaleMode) -{ - resolution = resolution || 1; - - var bounds = this.getBounds(); - - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); - - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - canvasBuffer.context.scale(resolution, resolution); - - canvasBuffer.context.translate(-bounds.x,-bounds.y); - - CanvasGraphics.renderGraphics(this, canvasBuffer.context); - - return texture; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (this.isMask === true) - { - return; - } - - // this code may still be needed so leaving for now.. - // - /* - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture on the gpu too! - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.worldAlpha = this.worldAlpha; - - Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); - - return; - } - - */ - - if (this.glDirty) - { - this.dirty = true; - this.glDirty = false; - } - - renderer.setObjectRenderer(renderer.objectRenderers.graphics); - renderer.objectRenderers.graphics.render(this); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} - * @private - */ -Graphics.prototype.renderCanvas = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.alpha <= 0 || this.isMask === true) - { - return; - } - - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.alpha = this.alpha; - - Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); - - return; - } - else - { - var context = renderer.context; - var transform = this.worldTransform; - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - var resolution = renderer.resolution; - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } - } -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @return {Rectangle} the rectangular bounding area - */ -Graphics.prototype.getBounds = function (matrix) -{ - // return an empty object if the item is a mask! - if (this.isMask) - { - return math.Rectangle.EMPTY; - } - - if (this.dirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.dirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - return this._bounds; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; - - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; -}; - -/** - * Generates the cached sprite when the sprite has cacheAsBitmap = true - * - * @private - */ -Graphics.prototype._generateCachedSprite = function () -{ - var bounds = this.getLocalBounds(); - - if (!this._cachedSprite) - { - var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); - var texture = Texture.fromCanvas(canvasBuffer.canvas); - - this._cachedSprite = new Sprite(texture); - this._cachedSprite.buffer = canvasBuffer; - - this._cachedSprite.worldTransform = this.worldTransform; - } - else - { - this._cachedSprite.buffer.resize(bounds.width, bounds.height); - } - - // leverage the anchor to account for the offset of the element - this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); - this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); - - // this._cachedSprite.buffer.context.save(); - this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); - - // make sure we set the alpha of the graphics to 1 for the render.. - this.worldAlpha = 1; - - // now render the graphic.. - CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); - - this._cachedSprite.alpha = this.alpha; -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Graphics.prototype.updateCachedSpriteTexture = function () -{ - var cachedSprite = this._cachedSprite; - var texture = cachedSprite.texture; - var canvas = cachedSprite.buffer.canvas; - - texture.baseTexture.width = canvas.width; - texture.baseTexture.height = canvas.height; - texture.crop.width = texture.frame.width = canvas.width; - texture.crop.height = texture.frame.height = canvas.height; - - cachedSprite._width = canvas.width; - cachedSprite._height = canvas.height; - - // update the dirty base textures - texture.baseTexture.dirty(); -}; - -/** - * Destroys a previous cached sprite. - * - */ -Graphics.prototype.destroyCachedSprite = function () -{ - this._cachedSprite.texture.destroy(true); - - // let the gc collect the unused sprite - // TODO could be object pooled! - this._cachedSprite = null; -}; - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. - * @return {GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = this.filling; - this.currentPath = data; - } - - this.dirty = true; - - return data; -}; diff --git a/src/core/primitives/GraphicsData.js b/src/core/primitives/GraphicsData.js deleted file mode 100644 index 788f6f2..0000000 --- a/src/core/primitives/GraphicsData.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * A GraphicsData object. - * - * @class - * @namespace PIXI - */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - this.lineWidth = lineWidth; - this.lineColor = lineColor; - this.lineAlpha = lineAlpha; - this._lineTint = lineColor; - - this.fillColor = fillColor; - this.fillAlpha = fillAlpha; - this._fillTint = fillColor; - this.fill = fill; - - this.shape = shape; - this.type = shape.type; -} - -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {GraphicsData} - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js deleted file mode 100644 index 1a508f9..0000000 --- a/src/core/primitives/GraphicsRenderer.js +++ /dev/null @@ -1,874 +0,0 @@ -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; - -/** - * 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 ); - -// var matrix = graphics.worldTransform.clone(); -// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); -// matrix.append(graphics.worldTransform); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.maskManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.maskManager.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.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); - - 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 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js new file mode 100644 index 0000000..41be102 --- /dev/null +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -0,0 +1,48 @@ +/** + * @class + * @private + */ +function WebGLGraphicsData(gl) { + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +} + +WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; +module.exports = WebGLGraphicsData; + +/** + * + */ +WebGLGraphicsData.prototype.reset = function () { + this.points = []; + this.indices = []; +}; + +/** + * + */ +WebGLGraphicsData.prototype.upload = function () { + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; diff --git a/src/core/index.js b/src/core/index.js index 6f744e9..1d0a398 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -23,12 +23,12 @@ Sprite: require('./sprites/Sprite'), SpriteBatch: require('./sprites/SpriteBatch'), - SpriteRenderer: require('./sprites/SpriteRenderer'), + SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), // primitives - Graphics: require('./primitives/Graphics'), - GraphicsData: require('./primitives/GraphicsData'), - GraphicsRenderer: require('./primitives/GraphicsRenderer'), + Graphics: require('./graphics/Graphics'), + GraphicsData: require('./graphics/GraphicsData'), + GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,10 +42,8 @@ CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), // renderers - webgl - WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), - // WebGLGraphics: require('./renderers/webgl/utils/WebGLGraphics'), - - + WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), + Shader: require('./renderers/webgl/shaders/Shader'), /** * This helper function will automatically detect which renderer you should be using. @@ -103,7 +101,3 @@ return core.autoDetectRenderer(width, height, options, isAndroid); } }; - -// Adding here for now.. sure there is a better place! -core.WebGLRenderer.registerObjectRenderer('sprite', core.SpriteRenderer); -core.WebGLRenderer.registerObjectRenderer('graphics', core.GraphicsRenderer); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index a086950..81de05c 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -268,6 +268,6 @@ matrix.ty = this.ty; return matrix; -} +}; Matrix.IDENTITY = new Matrix(); diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js deleted file mode 100644 index 8db9b45..0000000 --- a/src/core/primitives/Graphics.js +++ /dev/null @@ -1,1116 +0,0 @@ -var DisplayObjectContainer = require('../display/DisplayObjectContainer'), - //Sprite = require('../display/Sprite'), - Texture = require('../textures/Texture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), - // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), - GraphicsData = require('./GraphicsData'), - math = require('../math'), - CONST = require('../const'); - -/** - * The Graphics class contains methods used to draw primitive shapes such as lines, circles and - * rectangles to the display, and color and fill them. - * - * @class - * @extends DisplayObjectContainer - * @namespace PIXI - */ -function Graphics() -{ - DisplayObjectContainer.call(this); - - this.renderable = true; - - /** - * The alpha value used when filling the Graphics object. - * - * @member {number} - * @default 1 - */ - this.fillAlpha = 1; - - /** - * The width (thickness) of any lines drawn. - * - * @member {number} - * @default 0 - */ - this.lineWidth = 0; - - /** - * The color of any lines drawn. - * - * @member {string} - * @default 0 - */ - this.lineColor = 0; - - /** - * Graphics data - * - * @member {GraphicsData[]} - * @private - */ - this.graphicsData = []; - - /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * Current path - * - * @member {GraphicsData} - * @private - */ - this.currentPath = null; - - /** - * Array containing some WebGL-related properties used by the WebGL renderer. - * - * @member {object} - * @private - */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; - - /** - * Whether this shape is being used as a mask. - * - * @member {boolean} - */ - this.isMask = false; - - /** - * The bounds' padding used for bounds calculation. - * - * @member {number} - */ - this.boundsPadding = 0; - - /** - * A cache of the local bounds to prevent recalculation. - * - * @member {Rectangle} - * @private - */ - this._localBounds = new math.Rectangle(0,0,1,1); - - /** - * Used to detect if the graphics object has changed. If this is set to true then the graphics - * object will be recalculated. - * - * @member {boolean} - * @private - */ - this.dirty = true; - - /** - * Used to detect if the WebGL graphics object has changed. If this is set to true then the - * graphics object will be recalculated. - * - * @member {boolean} - * @private - */ - this.glDirty = false; - - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; -} - -// constructor -Graphics.prototype = Object.create(DisplayObjectContainer.prototype); -Graphics.prototype.constructor = Graphics; -module.exports = Graphics; - -Object.defineProperties(Graphics.prototype, { - /** - * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. - * This is useful if your graphics element does not change often, as it will speed up the rendering - * of the object in exchange for taking up texture memory. It is also useful if you need the graphics - * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if - * you are constantly redrawing the graphics element. - * - * @member {boolean} - * @memberof Graphics# - * @default false - * @private - */ - cacheAsBitmap: { - get: function () - { - return this._cacheAsBitmap; - }, - set: function (value) - { - this._cacheAsBitmap = value; - - if (this._cacheAsBitmap) - { - this._generateCachedSprite(); - } - else - { - this.destroyCachedSprite(); - this.dirty = true; - } - } - } -}); - -/** - * Creates a new Graphics object with the same values as this one. - * - * @return {Graphics} - */ -GraphicsData.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = this.dirty; - clone.glDirty = this.glDirty; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData.clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {Graphics} - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (arguments.length < 3) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); - } - else - { - // otherwise its empty so lets just set the line properties - this.currentPath.lineWidth = this.lineWidth; - this.currentPath.lineColor = this.lineColor; - this.currentPath.lineAlpha = this.lineAlpha; - } - } - - return this; -}; - -/** - * Moves the current drawing position to x, y. - * - * @param x {number} the X coordinate to move to - * @param y {number} the Y coordinate to move to - * @return {Graphics} - */ -Graphics.prototype.moveTo = function (x, y) -{ - this.drawShape(new math.Polygon([x,y])); - - return this; -}; - -/** - * Draws a line using the current line style from the current drawing position to (x, y); - * The current drawing position is then set to (x, y). - * - * @param x {number} the X coordinate to draw to - * @param y {number} the Y coordinate to draw to - * @return {Graphics} - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a quadratic bezier curve and then draws it. - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @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 {Graphics} - */ -Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var xa, - ya, - n = 20, - points = this.currentPath.shape.points; - - if (points.length === 0) - { - this.moveTo(0, 0); - } - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - for (var i = 1; i <= n; ++i) - { - j = i / n; - - xa = fromX + ( (cpX - fromX) * j ); - ya = fromY + ( (cpY - fromY) * j ); - - points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), - ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); - } - - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a bezier curve and then draws it. - * - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param cpX2 {number} Second Control point x - * @param cpY2 {number} Second Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {Graphics} - */ -Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var n = 20, - dt, - dt2, - dt3, - t2, - t3, - points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - - for (var i = 1; i <= n; ++i) - { - j = i / n; - - dt = (1 - j); - dt2 = dt * dt; - dt3 = dt2 * dt; - - t2 = j * j; - t3 = t2 * j; - - points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, - dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); - } - - this.dirty = true; - - return this; -}; - -/** - * The arcTo() method creates an arc/curve between two tangents on the canvas. - * - * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! - * - * @param x1 {number} The x-coordinate of the beginning of the arc - * @param y1 {number} The y-coordinate of the beginning of the arc - * @param x2 {number} The x-coordinate of the end of the arc - * @param y2 {number} The y-coordinate of the end of the arc - * @param radius {number} The radius of the arc - * @return {Graphics} - */ -Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points.push(x1, y1); - } - } - else - { - this.moveTo(x1, y1); - } - - var points = this.currentPath.shape.points, - fromX = points[points.length-2], - fromY = points[points.length-1], - a1 = fromY - y1, - b1 = fromX - x1, - a2 = y2 - y1, - b2 = x2 - x1, - mm = Math.abs(a1 * b2 - b1 * a2); - - if (mm < 1.0e-8 || radius === 0) - { - if (points[points.length-2] !== x1 || points[points.length-1] !== y1) - { - points.push(x1, y1); - } - } - else - { - var dd = a1 * a1 + b1 * b1, - cc = a2 * a2 + b2 * b2, - tt = a1 * a2 + b1 * b2, - k1 = radius * Math.sqrt(dd) / mm, - k2 = radius * Math.sqrt(cc) / mm, - j1 = k1 * tt / dd, - j2 = k2 * tt / cc, - cx = k1 * b2 + k2 * b1, - cy = k1 * a2 + k2 * a1, - px = b1 * (k2 + j1), - py = a1 * (k2 + j1), - qx = b2 * (k1 + j2), - qy = a2 * (k1 + j2), - startAngle = Math.atan2(py - cy, px - cx), - endAngle = Math.atan2(qy - cy, qx - cx); - - this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); - } - - this.dirty = true; - - return this; -}; - -/** - * The arc method creates an arc/curve (used to create circles, or parts of circles). - * - * @param cx {number} The x-coordinate of the center of the circle - * @param cy {number} The y-coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) - * @param endAngle {number} The ending angle, in radians - * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {Graphics} - */ -Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - var points; - - // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... - if (this.currentPath) - { - points = this.currentPath.shape.points; - - if (points.length === 0) - { - points.push(startX, startY); - } - else if (points[points.length-2] !== startX || points[points.length-1] !== startY) - { - points.push(startX, startY); - } - } - else - { - this.moveTo(startX, startY); - points = this.currentPath.shape.points; - } - - if (startAngle === endAngle) - { - return this; - } - - if (!anticlockwise && endAngle <= startAngle) - { - endAngle += Math.PI * 2; - } - else if (anticlockwise && startAngle <= endAngle) - { - startAngle += Math.PI * 2; - } - - var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); - var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; - - if (sweep === 0) - { - return this; - } - - var theta = sweep/(segs*2); - var theta2 = theta*2; - - var cTheta = Math.cos(theta); - var sTheta = Math.sin(theta); - - var segMinus = segs - 1; - - var remainder = ( segMinus % 1 ) / segMinus; - - for (var i = 0; i <= segMinus; ++i) - { - var real = i + remainder * i; - var angle = ((theta) + startAngle + (theta2 * real)); - - var c = Math.cos(angle); - var s = -Math.sin(angle); - - points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, - ( (cTheta * -s) + (sTheta * c) ) * radius + cy); - } - - this.dirty = true; - - return this; -}; - -/** - * Specifies a simple one-color fill that subsequent calls to other Graphics methods - * (such as lineTo() or drawCircle()) use when drawing. - * - * @param color {number} the color of the fill - * @param alpha {number} the alpha of the fill - * @return {Graphics} - */ -Graphics.prototype.beginFill = function (color, alpha) -{ - this.filling = true; - this.fillColor = color || 0; - this.fillAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length <= 2) - { - this.currentPath.fill = this.filling; - this.currentPath.fillColor = this.fillColor; - this.currentPath.fillAlpha = this.fillAlpha; - } - } - return this; -}; - -/** - * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. - * - * @return {Graphics} - */ -Graphics.prototype.endFill = function () -{ - this.filling = false; - this.fillColor = null; - this.fillAlpha = 1; - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @return {Graphics} - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {Graphics} - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {Graphics} - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {Array} The path data used to construct the polygon. - * @return {Graphics} - */ -Graphics.prototype.drawPolygon = function (path) -{ - if (!(path instanceof Array)) - { - path = Array.prototype.slice.call(arguments); - } - - this.drawShape(new math.Polygon(path)); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {Graphics} - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty = true; - this.clearDirty = true; - this.graphicsData = []; - - return this; -}; - -/** - * Useful function that returns a texture of the graphics object that can then be used to create sprites - * This can be quite useful if your geometry is complicated and needs to be reused multiple times. - * - * @param resolution {number} The resolution of the texture being generated - * @param scaleMode {number} Should be one of the scaleMode consts - * @return {Texture} a texture of the graphics object - */ -Graphics.prototype.generateTexture = function (resolution, scaleMode) -{ - resolution = resolution || 1; - - var bounds = this.getBounds(); - - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); - - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - canvasBuffer.context.scale(resolution, resolution); - - canvasBuffer.context.translate(-bounds.x,-bounds.y); - - CanvasGraphics.renderGraphics(this, canvasBuffer.context); - - return texture; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (this.isMask === true) - { - return; - } - - // this code may still be needed so leaving for now.. - // - /* - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture on the gpu too! - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.worldAlpha = this.worldAlpha; - - Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); - - return; - } - - */ - - if (this.glDirty) - { - this.dirty = true; - this.glDirty = false; - } - - renderer.setObjectRenderer(renderer.objectRenderers.graphics); - renderer.objectRenderers.graphics.render(this); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} - * @private - */ -Graphics.prototype.renderCanvas = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.alpha <= 0 || this.isMask === true) - { - return; - } - - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.alpha = this.alpha; - - Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); - - return; - } - else - { - var context = renderer.context; - var transform = this.worldTransform; - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - var resolution = renderer.resolution; - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } - } -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @return {Rectangle} the rectangular bounding area - */ -Graphics.prototype.getBounds = function (matrix) -{ - // return an empty object if the item is a mask! - if (this.isMask) - { - return math.Rectangle.EMPTY; - } - - if (this.dirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.dirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - return this._bounds; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; - - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; -}; - -/** - * Generates the cached sprite when the sprite has cacheAsBitmap = true - * - * @private - */ -Graphics.prototype._generateCachedSprite = function () -{ - var bounds = this.getLocalBounds(); - - if (!this._cachedSprite) - { - var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); - var texture = Texture.fromCanvas(canvasBuffer.canvas); - - this._cachedSprite = new Sprite(texture); - this._cachedSprite.buffer = canvasBuffer; - - this._cachedSprite.worldTransform = this.worldTransform; - } - else - { - this._cachedSprite.buffer.resize(bounds.width, bounds.height); - } - - // leverage the anchor to account for the offset of the element - this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); - this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); - - // this._cachedSprite.buffer.context.save(); - this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); - - // make sure we set the alpha of the graphics to 1 for the render.. - this.worldAlpha = 1; - - // now render the graphic.. - CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); - - this._cachedSprite.alpha = this.alpha; -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Graphics.prototype.updateCachedSpriteTexture = function () -{ - var cachedSprite = this._cachedSprite; - var texture = cachedSprite.texture; - var canvas = cachedSprite.buffer.canvas; - - texture.baseTexture.width = canvas.width; - texture.baseTexture.height = canvas.height; - texture.crop.width = texture.frame.width = canvas.width; - texture.crop.height = texture.frame.height = canvas.height; - - cachedSprite._width = canvas.width; - cachedSprite._height = canvas.height; - - // update the dirty base textures - texture.baseTexture.dirty(); -}; - -/** - * Destroys a previous cached sprite. - * - */ -Graphics.prototype.destroyCachedSprite = function () -{ - this._cachedSprite.texture.destroy(true); - - // let the gc collect the unused sprite - // TODO could be object pooled! - this._cachedSprite = null; -}; - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. - * @return {GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = this.filling; - this.currentPath = data; - } - - this.dirty = true; - - return data; -}; diff --git a/src/core/primitives/GraphicsData.js b/src/core/primitives/GraphicsData.js deleted file mode 100644 index 788f6f2..0000000 --- a/src/core/primitives/GraphicsData.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * A GraphicsData object. - * - * @class - * @namespace PIXI - */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - this.lineWidth = lineWidth; - this.lineColor = lineColor; - this.lineAlpha = lineAlpha; - this._lineTint = lineColor; - - this.fillColor = fillColor; - this.fillAlpha = fillAlpha; - this._fillTint = fillColor; - this.fill = fill; - - this.shape = shape; - this.type = shape.type; -} - -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {GraphicsData} - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js deleted file mode 100644 index 1a508f9..0000000 --- a/src/core/primitives/GraphicsRenderer.js +++ /dev/null @@ -1,874 +0,0 @@ -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; - -/** - * 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 ); - -// var matrix = graphics.worldTransform.clone(); -// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); -// matrix.append(graphics.worldTransform); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.maskManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.maskManager.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.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); - - 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/primitives/WebGLGraphicsData.js b/src/core/primitives/WebGLGraphicsData.js deleted file mode 100755 index 41be102..0000000 --- a/src/core/primitives/WebGLGraphicsData.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @class - * @private - */ -function WebGLGraphicsData(gl) { - this.gl = gl; - - //TODO does this need to be split before uploding?? - this.color = [0,0,0]; // color split! - this.points = []; - this.indices = []; - this.buffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - this.mode = 1; - this.alpha = 1; - this.dirty = true; -} - -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; -module.exports = WebGLGraphicsData; - -/** - * - */ -WebGLGraphicsData.prototype.reset = function () { - this.points = []; - this.indices = []; -}; - -/** - * - */ -WebGLGraphicsData.prototype.upload = function () { - var gl = this.gl; - -// this.lastIndex = graphics.graphicsData.length; - this.glPoints = new Float32Array(this.points); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); - - this.glIndicies = new Uint16Array(this.indices); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); - - this.dirty = false; -}; diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js new file mode 100644 index 0000000..41be102 --- /dev/null +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -0,0 +1,48 @@ +/** + * @class + * @private + */ +function WebGLGraphicsData(gl) { + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +} + +WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; +module.exports = WebGLGraphicsData; + +/** + * + */ +WebGLGraphicsData.prototype.reset = function () { + this.points = []; + this.indices = []; +}; + +/** + * + */ +WebGLGraphicsData.prototype.upload = function () { + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; diff --git a/src/core/index.js b/src/core/index.js index 6f744e9..1d0a398 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -23,12 +23,12 @@ Sprite: require('./sprites/Sprite'), SpriteBatch: require('./sprites/SpriteBatch'), - SpriteRenderer: require('./sprites/SpriteRenderer'), + SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), // primitives - Graphics: require('./primitives/Graphics'), - GraphicsData: require('./primitives/GraphicsData'), - GraphicsRenderer: require('./primitives/GraphicsRenderer'), + Graphics: require('./graphics/Graphics'), + GraphicsData: require('./graphics/GraphicsData'), + GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,10 +42,8 @@ CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), // renderers - webgl - WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), - // WebGLGraphics: require('./renderers/webgl/utils/WebGLGraphics'), - - + WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), + Shader: require('./renderers/webgl/shaders/Shader'), /** * This helper function will automatically detect which renderer you should be using. @@ -103,7 +101,3 @@ return core.autoDetectRenderer(width, height, options, isAndroid); } }; - -// Adding here for now.. sure there is a better place! -core.WebGLRenderer.registerObjectRenderer('sprite', core.SpriteRenderer); -core.WebGLRenderer.registerObjectRenderer('graphics', core.GraphicsRenderer); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index a086950..81de05c 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -268,6 +268,6 @@ matrix.ty = this.ty; return matrix; -} +}; Matrix.IDENTITY = new Matrix(); diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js deleted file mode 100644 index 8db9b45..0000000 --- a/src/core/primitives/Graphics.js +++ /dev/null @@ -1,1116 +0,0 @@ -var DisplayObjectContainer = require('../display/DisplayObjectContainer'), - //Sprite = require('../display/Sprite'), - Texture = require('../textures/Texture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), - // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), - GraphicsData = require('./GraphicsData'), - math = require('../math'), - CONST = require('../const'); - -/** - * The Graphics class contains methods used to draw primitive shapes such as lines, circles and - * rectangles to the display, and color and fill them. - * - * @class - * @extends DisplayObjectContainer - * @namespace PIXI - */ -function Graphics() -{ - DisplayObjectContainer.call(this); - - this.renderable = true; - - /** - * The alpha value used when filling the Graphics object. - * - * @member {number} - * @default 1 - */ - this.fillAlpha = 1; - - /** - * The width (thickness) of any lines drawn. - * - * @member {number} - * @default 0 - */ - this.lineWidth = 0; - - /** - * The color of any lines drawn. - * - * @member {string} - * @default 0 - */ - this.lineColor = 0; - - /** - * Graphics data - * - * @member {GraphicsData[]} - * @private - */ - this.graphicsData = []; - - /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * Current path - * - * @member {GraphicsData} - * @private - */ - this.currentPath = null; - - /** - * Array containing some WebGL-related properties used by the WebGL renderer. - * - * @member {object} - * @private - */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; - - /** - * Whether this shape is being used as a mask. - * - * @member {boolean} - */ - this.isMask = false; - - /** - * The bounds' padding used for bounds calculation. - * - * @member {number} - */ - this.boundsPadding = 0; - - /** - * A cache of the local bounds to prevent recalculation. - * - * @member {Rectangle} - * @private - */ - this._localBounds = new math.Rectangle(0,0,1,1); - - /** - * Used to detect if the graphics object has changed. If this is set to true then the graphics - * object will be recalculated. - * - * @member {boolean} - * @private - */ - this.dirty = true; - - /** - * Used to detect if the WebGL graphics object has changed. If this is set to true then the - * graphics object will be recalculated. - * - * @member {boolean} - * @private - */ - this.glDirty = false; - - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; -} - -// constructor -Graphics.prototype = Object.create(DisplayObjectContainer.prototype); -Graphics.prototype.constructor = Graphics; -module.exports = Graphics; - -Object.defineProperties(Graphics.prototype, { - /** - * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. - * This is useful if your graphics element does not change often, as it will speed up the rendering - * of the object in exchange for taking up texture memory. It is also useful if you need the graphics - * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if - * you are constantly redrawing the graphics element. - * - * @member {boolean} - * @memberof Graphics# - * @default false - * @private - */ - cacheAsBitmap: { - get: function () - { - return this._cacheAsBitmap; - }, - set: function (value) - { - this._cacheAsBitmap = value; - - if (this._cacheAsBitmap) - { - this._generateCachedSprite(); - } - else - { - this.destroyCachedSprite(); - this.dirty = true; - } - } - } -}); - -/** - * Creates a new Graphics object with the same values as this one. - * - * @return {Graphics} - */ -GraphicsData.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = this.dirty; - clone.glDirty = this.glDirty; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData.clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {Graphics} - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (arguments.length < 3) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); - } - else - { - // otherwise its empty so lets just set the line properties - this.currentPath.lineWidth = this.lineWidth; - this.currentPath.lineColor = this.lineColor; - this.currentPath.lineAlpha = this.lineAlpha; - } - } - - return this; -}; - -/** - * Moves the current drawing position to x, y. - * - * @param x {number} the X coordinate to move to - * @param y {number} the Y coordinate to move to - * @return {Graphics} - */ -Graphics.prototype.moveTo = function (x, y) -{ - this.drawShape(new math.Polygon([x,y])); - - return this; -}; - -/** - * Draws a line using the current line style from the current drawing position to (x, y); - * The current drawing position is then set to (x, y). - * - * @param x {number} the X coordinate to draw to - * @param y {number} the Y coordinate to draw to - * @return {Graphics} - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a quadratic bezier curve and then draws it. - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @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 {Graphics} - */ -Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var xa, - ya, - n = 20, - points = this.currentPath.shape.points; - - if (points.length === 0) - { - this.moveTo(0, 0); - } - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - for (var i = 1; i <= n; ++i) - { - j = i / n; - - xa = fromX + ( (cpX - fromX) * j ); - ya = fromY + ( (cpY - fromY) * j ); - - points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), - ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); - } - - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a bezier curve and then draws it. - * - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param cpX2 {number} Second Control point x - * @param cpY2 {number} Second Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {Graphics} - */ -Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var n = 20, - dt, - dt2, - dt3, - t2, - t3, - points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - - for (var i = 1; i <= n; ++i) - { - j = i / n; - - dt = (1 - j); - dt2 = dt * dt; - dt3 = dt2 * dt; - - t2 = j * j; - t3 = t2 * j; - - points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, - dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); - } - - this.dirty = true; - - return this; -}; - -/** - * The arcTo() method creates an arc/curve between two tangents on the canvas. - * - * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! - * - * @param x1 {number} The x-coordinate of the beginning of the arc - * @param y1 {number} The y-coordinate of the beginning of the arc - * @param x2 {number} The x-coordinate of the end of the arc - * @param y2 {number} The y-coordinate of the end of the arc - * @param radius {number} The radius of the arc - * @return {Graphics} - */ -Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points.push(x1, y1); - } - } - else - { - this.moveTo(x1, y1); - } - - var points = this.currentPath.shape.points, - fromX = points[points.length-2], - fromY = points[points.length-1], - a1 = fromY - y1, - b1 = fromX - x1, - a2 = y2 - y1, - b2 = x2 - x1, - mm = Math.abs(a1 * b2 - b1 * a2); - - if (mm < 1.0e-8 || radius === 0) - { - if (points[points.length-2] !== x1 || points[points.length-1] !== y1) - { - points.push(x1, y1); - } - } - else - { - var dd = a1 * a1 + b1 * b1, - cc = a2 * a2 + b2 * b2, - tt = a1 * a2 + b1 * b2, - k1 = radius * Math.sqrt(dd) / mm, - k2 = radius * Math.sqrt(cc) / mm, - j1 = k1 * tt / dd, - j2 = k2 * tt / cc, - cx = k1 * b2 + k2 * b1, - cy = k1 * a2 + k2 * a1, - px = b1 * (k2 + j1), - py = a1 * (k2 + j1), - qx = b2 * (k1 + j2), - qy = a2 * (k1 + j2), - startAngle = Math.atan2(py - cy, px - cx), - endAngle = Math.atan2(qy - cy, qx - cx); - - this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); - } - - this.dirty = true; - - return this; -}; - -/** - * The arc method creates an arc/curve (used to create circles, or parts of circles). - * - * @param cx {number} The x-coordinate of the center of the circle - * @param cy {number} The y-coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) - * @param endAngle {number} The ending angle, in radians - * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {Graphics} - */ -Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - var points; - - // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... - if (this.currentPath) - { - points = this.currentPath.shape.points; - - if (points.length === 0) - { - points.push(startX, startY); - } - else if (points[points.length-2] !== startX || points[points.length-1] !== startY) - { - points.push(startX, startY); - } - } - else - { - this.moveTo(startX, startY); - points = this.currentPath.shape.points; - } - - if (startAngle === endAngle) - { - return this; - } - - if (!anticlockwise && endAngle <= startAngle) - { - endAngle += Math.PI * 2; - } - else if (anticlockwise && startAngle <= endAngle) - { - startAngle += Math.PI * 2; - } - - var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); - var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; - - if (sweep === 0) - { - return this; - } - - var theta = sweep/(segs*2); - var theta2 = theta*2; - - var cTheta = Math.cos(theta); - var sTheta = Math.sin(theta); - - var segMinus = segs - 1; - - var remainder = ( segMinus % 1 ) / segMinus; - - for (var i = 0; i <= segMinus; ++i) - { - var real = i + remainder * i; - var angle = ((theta) + startAngle + (theta2 * real)); - - var c = Math.cos(angle); - var s = -Math.sin(angle); - - points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, - ( (cTheta * -s) + (sTheta * c) ) * radius + cy); - } - - this.dirty = true; - - return this; -}; - -/** - * Specifies a simple one-color fill that subsequent calls to other Graphics methods - * (such as lineTo() or drawCircle()) use when drawing. - * - * @param color {number} the color of the fill - * @param alpha {number} the alpha of the fill - * @return {Graphics} - */ -Graphics.prototype.beginFill = function (color, alpha) -{ - this.filling = true; - this.fillColor = color || 0; - this.fillAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length <= 2) - { - this.currentPath.fill = this.filling; - this.currentPath.fillColor = this.fillColor; - this.currentPath.fillAlpha = this.fillAlpha; - } - } - return this; -}; - -/** - * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. - * - * @return {Graphics} - */ -Graphics.prototype.endFill = function () -{ - this.filling = false; - this.fillColor = null; - this.fillAlpha = 1; - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @return {Graphics} - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {Graphics} - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {Graphics} - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {Array} The path data used to construct the polygon. - * @return {Graphics} - */ -Graphics.prototype.drawPolygon = function (path) -{ - if (!(path instanceof Array)) - { - path = Array.prototype.slice.call(arguments); - } - - this.drawShape(new math.Polygon(path)); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {Graphics} - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty = true; - this.clearDirty = true; - this.graphicsData = []; - - return this; -}; - -/** - * Useful function that returns a texture of the graphics object that can then be used to create sprites - * This can be quite useful if your geometry is complicated and needs to be reused multiple times. - * - * @param resolution {number} The resolution of the texture being generated - * @param scaleMode {number} Should be one of the scaleMode consts - * @return {Texture} a texture of the graphics object - */ -Graphics.prototype.generateTexture = function (resolution, scaleMode) -{ - resolution = resolution || 1; - - var bounds = this.getBounds(); - - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); - - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - canvasBuffer.context.scale(resolution, resolution); - - canvasBuffer.context.translate(-bounds.x,-bounds.y); - - CanvasGraphics.renderGraphics(this, canvasBuffer.context); - - return texture; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (this.isMask === true) - { - return; - } - - // this code may still be needed so leaving for now.. - // - /* - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture on the gpu too! - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.worldAlpha = this.worldAlpha; - - Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); - - return; - } - - */ - - if (this.glDirty) - { - this.dirty = true; - this.glDirty = false; - } - - renderer.setObjectRenderer(renderer.objectRenderers.graphics); - renderer.objectRenderers.graphics.render(this); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} - * @private - */ -Graphics.prototype.renderCanvas = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.alpha <= 0 || this.isMask === true) - { - return; - } - - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.alpha = this.alpha; - - Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); - - return; - } - else - { - var context = renderer.context; - var transform = this.worldTransform; - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - var resolution = renderer.resolution; - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } - } -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @return {Rectangle} the rectangular bounding area - */ -Graphics.prototype.getBounds = function (matrix) -{ - // return an empty object if the item is a mask! - if (this.isMask) - { - return math.Rectangle.EMPTY; - } - - if (this.dirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.dirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - return this._bounds; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; - - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; -}; - -/** - * Generates the cached sprite when the sprite has cacheAsBitmap = true - * - * @private - */ -Graphics.prototype._generateCachedSprite = function () -{ - var bounds = this.getLocalBounds(); - - if (!this._cachedSprite) - { - var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); - var texture = Texture.fromCanvas(canvasBuffer.canvas); - - this._cachedSprite = new Sprite(texture); - this._cachedSprite.buffer = canvasBuffer; - - this._cachedSprite.worldTransform = this.worldTransform; - } - else - { - this._cachedSprite.buffer.resize(bounds.width, bounds.height); - } - - // leverage the anchor to account for the offset of the element - this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); - this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); - - // this._cachedSprite.buffer.context.save(); - this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); - - // make sure we set the alpha of the graphics to 1 for the render.. - this.worldAlpha = 1; - - // now render the graphic.. - CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); - - this._cachedSprite.alpha = this.alpha; -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Graphics.prototype.updateCachedSpriteTexture = function () -{ - var cachedSprite = this._cachedSprite; - var texture = cachedSprite.texture; - var canvas = cachedSprite.buffer.canvas; - - texture.baseTexture.width = canvas.width; - texture.baseTexture.height = canvas.height; - texture.crop.width = texture.frame.width = canvas.width; - texture.crop.height = texture.frame.height = canvas.height; - - cachedSprite._width = canvas.width; - cachedSprite._height = canvas.height; - - // update the dirty base textures - texture.baseTexture.dirty(); -}; - -/** - * Destroys a previous cached sprite. - * - */ -Graphics.prototype.destroyCachedSprite = function () -{ - this._cachedSprite.texture.destroy(true); - - // let the gc collect the unused sprite - // TODO could be object pooled! - this._cachedSprite = null; -}; - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. - * @return {GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = this.filling; - this.currentPath = data; - } - - this.dirty = true; - - return data; -}; diff --git a/src/core/primitives/GraphicsData.js b/src/core/primitives/GraphicsData.js deleted file mode 100644 index 788f6f2..0000000 --- a/src/core/primitives/GraphicsData.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * A GraphicsData object. - * - * @class - * @namespace PIXI - */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - this.lineWidth = lineWidth; - this.lineColor = lineColor; - this.lineAlpha = lineAlpha; - this._lineTint = lineColor; - - this.fillColor = fillColor; - this.fillAlpha = fillAlpha; - this._fillTint = fillColor; - this.fill = fill; - - this.shape = shape; - this.type = shape.type; -} - -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {GraphicsData} - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js deleted file mode 100644 index 1a508f9..0000000 --- a/src/core/primitives/GraphicsRenderer.js +++ /dev/null @@ -1,874 +0,0 @@ -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; - -/** - * 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 ); - -// var matrix = graphics.worldTransform.clone(); -// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); -// matrix.append(graphics.worldTransform); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.maskManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.maskManager.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.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); - - 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/primitives/WebGLGraphicsData.js b/src/core/primitives/WebGLGraphicsData.js deleted file mode 100755 index 41be102..0000000 --- a/src/core/primitives/WebGLGraphicsData.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @class - * @private - */ -function WebGLGraphicsData(gl) { - this.gl = gl; - - //TODO does this need to be split before uploding?? - this.color = [0,0,0]; // color split! - this.points = []; - this.indices = []; - this.buffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - this.mode = 1; - this.alpha = 1; - this.dirty = true; -} - -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; -module.exports = WebGLGraphicsData; - -/** - * - */ -WebGLGraphicsData.prototype.reset = function () { - this.points = []; - this.indices = []; -}; - -/** - * - */ -WebGLGraphicsData.prototype.upload = function () { - var gl = this.gl; - -// this.lastIndex = graphics.graphicsData.length; - this.glPoints = new Float32Array(this.points); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); - - this.glIndicies = new Uint16Array(this.indices); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); - - this.dirty = false; -}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6c1fb7d..4e82baa 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -174,6 +174,9 @@ // time to create the render managers! each one focuses on managing a state in webGL + // initialize the context so it is ready for the managers. + this._initContext(); + /** * Deals with managing the shader programs and their attribs * @member {WebGLShaderManager} @@ -204,17 +207,11 @@ */ this.blendModeManager = new WebGLBlendModeManager(this); - - this.blendModes = null; this._boundUpdateTexture = this.updateTexture.bind(this); this._boundDestroyTexture = this.destroyTexture.bind(this); - - - // time init the context.. - this._initContext(); this.currentRenderTarget = this.renderTarget; @@ -228,27 +225,18 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - /** - * Manages the renderer of specific objects. - * - * @member {object} - */ - this.objectRenderers = {}; - this.currentRenderer = new ObjectRenderer(); - - // create an instance of each registered object renderer - for (var o in WebGLRenderer._objectRenderers) { - this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); - } - + this.initPlugins(); } // constructor WebGLRenderer.prototype.constructor = WebGLRenderer; module.exports = WebGLRenderer; +WebGLRenderer.glContextId = 0; + +utils.pluginTarget.mixin(WebGLRenderer); utils.eventTarget.mixin(WebGLRenderer.prototype); Object.defineProperties(WebGLRenderer.prototype, { @@ -297,11 +285,9 @@ gl.enable(gl.BLEND); this.renderTarget = new RenderTarget(this.gl, this.width, this.height, null, true); - this.emit('context', gl); - // now resize and we are good to go! this.resize(this.width, this.height); }; @@ -349,7 +335,7 @@ gl.clear(gl.COLOR_BUFFER_BIT); } - this.renderDisplayObject(object, this.renderTarget)//this.projection); + this.renderDisplayObject(object, this.renderTarget);//this.projection); }; /** @@ -536,12 +522,7 @@ this.maskManager.destroy(); this.filterManager.destroy(); - for (var o in this.objectRenderers) { - this.objectRenderers[o].destroy(); - this.objectRenderers[o] = null; - } - - this.objectRenderers = null; + this.destroyPlugins(); // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -611,11 +592,3 @@ this.blendModes[CONST.blendModes.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; } }; - -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 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js new file mode 100644 index 0000000..41be102 --- /dev/null +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -0,0 +1,48 @@ +/** + * @class + * @private + */ +function WebGLGraphicsData(gl) { + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +} + +WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; +module.exports = WebGLGraphicsData; + +/** + * + */ +WebGLGraphicsData.prototype.reset = function () { + this.points = []; + this.indices = []; +}; + +/** + * + */ +WebGLGraphicsData.prototype.upload = function () { + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; diff --git a/src/core/index.js b/src/core/index.js index 6f744e9..1d0a398 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -23,12 +23,12 @@ Sprite: require('./sprites/Sprite'), SpriteBatch: require('./sprites/SpriteBatch'), - SpriteRenderer: require('./sprites/SpriteRenderer'), + SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), // primitives - Graphics: require('./primitives/Graphics'), - GraphicsData: require('./primitives/GraphicsData'), - GraphicsRenderer: require('./primitives/GraphicsRenderer'), + Graphics: require('./graphics/Graphics'), + GraphicsData: require('./graphics/GraphicsData'), + GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,10 +42,8 @@ CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), // renderers - webgl - WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), - // WebGLGraphics: require('./renderers/webgl/utils/WebGLGraphics'), - - + WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), + Shader: require('./renderers/webgl/shaders/Shader'), /** * This helper function will automatically detect which renderer you should be using. @@ -103,7 +101,3 @@ return core.autoDetectRenderer(width, height, options, isAndroid); } }; - -// Adding here for now.. sure there is a better place! -core.WebGLRenderer.registerObjectRenderer('sprite', core.SpriteRenderer); -core.WebGLRenderer.registerObjectRenderer('graphics', core.GraphicsRenderer); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index a086950..81de05c 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -268,6 +268,6 @@ matrix.ty = this.ty; return matrix; -} +}; Matrix.IDENTITY = new Matrix(); diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js deleted file mode 100644 index 8db9b45..0000000 --- a/src/core/primitives/Graphics.js +++ /dev/null @@ -1,1116 +0,0 @@ -var DisplayObjectContainer = require('../display/DisplayObjectContainer'), - //Sprite = require('../display/Sprite'), - Texture = require('../textures/Texture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), - // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), - GraphicsData = require('./GraphicsData'), - math = require('../math'), - CONST = require('../const'); - -/** - * The Graphics class contains methods used to draw primitive shapes such as lines, circles and - * rectangles to the display, and color and fill them. - * - * @class - * @extends DisplayObjectContainer - * @namespace PIXI - */ -function Graphics() -{ - DisplayObjectContainer.call(this); - - this.renderable = true; - - /** - * The alpha value used when filling the Graphics object. - * - * @member {number} - * @default 1 - */ - this.fillAlpha = 1; - - /** - * The width (thickness) of any lines drawn. - * - * @member {number} - * @default 0 - */ - this.lineWidth = 0; - - /** - * The color of any lines drawn. - * - * @member {string} - * @default 0 - */ - this.lineColor = 0; - - /** - * Graphics data - * - * @member {GraphicsData[]} - * @private - */ - this.graphicsData = []; - - /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * Current path - * - * @member {GraphicsData} - * @private - */ - this.currentPath = null; - - /** - * Array containing some WebGL-related properties used by the WebGL renderer. - * - * @member {object} - * @private - */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; - - /** - * Whether this shape is being used as a mask. - * - * @member {boolean} - */ - this.isMask = false; - - /** - * The bounds' padding used for bounds calculation. - * - * @member {number} - */ - this.boundsPadding = 0; - - /** - * A cache of the local bounds to prevent recalculation. - * - * @member {Rectangle} - * @private - */ - this._localBounds = new math.Rectangle(0,0,1,1); - - /** - * Used to detect if the graphics object has changed. If this is set to true then the graphics - * object will be recalculated. - * - * @member {boolean} - * @private - */ - this.dirty = true; - - /** - * Used to detect if the WebGL graphics object has changed. If this is set to true then the - * graphics object will be recalculated. - * - * @member {boolean} - * @private - */ - this.glDirty = false; - - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; -} - -// constructor -Graphics.prototype = Object.create(DisplayObjectContainer.prototype); -Graphics.prototype.constructor = Graphics; -module.exports = Graphics; - -Object.defineProperties(Graphics.prototype, { - /** - * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. - * This is useful if your graphics element does not change often, as it will speed up the rendering - * of the object in exchange for taking up texture memory. It is also useful if you need the graphics - * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if - * you are constantly redrawing the graphics element. - * - * @member {boolean} - * @memberof Graphics# - * @default false - * @private - */ - cacheAsBitmap: { - get: function () - { - return this._cacheAsBitmap; - }, - set: function (value) - { - this._cacheAsBitmap = value; - - if (this._cacheAsBitmap) - { - this._generateCachedSprite(); - } - else - { - this.destroyCachedSprite(); - this.dirty = true; - } - } - } -}); - -/** - * Creates a new Graphics object with the same values as this one. - * - * @return {Graphics} - */ -GraphicsData.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = this.dirty; - clone.glDirty = this.glDirty; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData.clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {Graphics} - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (arguments.length < 3) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); - } - else - { - // otherwise its empty so lets just set the line properties - this.currentPath.lineWidth = this.lineWidth; - this.currentPath.lineColor = this.lineColor; - this.currentPath.lineAlpha = this.lineAlpha; - } - } - - return this; -}; - -/** - * Moves the current drawing position to x, y. - * - * @param x {number} the X coordinate to move to - * @param y {number} the Y coordinate to move to - * @return {Graphics} - */ -Graphics.prototype.moveTo = function (x, y) -{ - this.drawShape(new math.Polygon([x,y])); - - return this; -}; - -/** - * Draws a line using the current line style from the current drawing position to (x, y); - * The current drawing position is then set to (x, y). - * - * @param x {number} the X coordinate to draw to - * @param y {number} the Y coordinate to draw to - * @return {Graphics} - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a quadratic bezier curve and then draws it. - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @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 {Graphics} - */ -Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var xa, - ya, - n = 20, - points = this.currentPath.shape.points; - - if (points.length === 0) - { - this.moveTo(0, 0); - } - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - for (var i = 1; i <= n; ++i) - { - j = i / n; - - xa = fromX + ( (cpX - fromX) * j ); - ya = fromY + ( (cpY - fromY) * j ); - - points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), - ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); - } - - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a bezier curve and then draws it. - * - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param cpX2 {number} Second Control point x - * @param cpY2 {number} Second Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {Graphics} - */ -Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var n = 20, - dt, - dt2, - dt3, - t2, - t3, - points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - - for (var i = 1; i <= n; ++i) - { - j = i / n; - - dt = (1 - j); - dt2 = dt * dt; - dt3 = dt2 * dt; - - t2 = j * j; - t3 = t2 * j; - - points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, - dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); - } - - this.dirty = true; - - return this; -}; - -/** - * The arcTo() method creates an arc/curve between two tangents on the canvas. - * - * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! - * - * @param x1 {number} The x-coordinate of the beginning of the arc - * @param y1 {number} The y-coordinate of the beginning of the arc - * @param x2 {number} The x-coordinate of the end of the arc - * @param y2 {number} The y-coordinate of the end of the arc - * @param radius {number} The radius of the arc - * @return {Graphics} - */ -Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points.push(x1, y1); - } - } - else - { - this.moveTo(x1, y1); - } - - var points = this.currentPath.shape.points, - fromX = points[points.length-2], - fromY = points[points.length-1], - a1 = fromY - y1, - b1 = fromX - x1, - a2 = y2 - y1, - b2 = x2 - x1, - mm = Math.abs(a1 * b2 - b1 * a2); - - if (mm < 1.0e-8 || radius === 0) - { - if (points[points.length-2] !== x1 || points[points.length-1] !== y1) - { - points.push(x1, y1); - } - } - else - { - var dd = a1 * a1 + b1 * b1, - cc = a2 * a2 + b2 * b2, - tt = a1 * a2 + b1 * b2, - k1 = radius * Math.sqrt(dd) / mm, - k2 = radius * Math.sqrt(cc) / mm, - j1 = k1 * tt / dd, - j2 = k2 * tt / cc, - cx = k1 * b2 + k2 * b1, - cy = k1 * a2 + k2 * a1, - px = b1 * (k2 + j1), - py = a1 * (k2 + j1), - qx = b2 * (k1 + j2), - qy = a2 * (k1 + j2), - startAngle = Math.atan2(py - cy, px - cx), - endAngle = Math.atan2(qy - cy, qx - cx); - - this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); - } - - this.dirty = true; - - return this; -}; - -/** - * The arc method creates an arc/curve (used to create circles, or parts of circles). - * - * @param cx {number} The x-coordinate of the center of the circle - * @param cy {number} The y-coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) - * @param endAngle {number} The ending angle, in radians - * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {Graphics} - */ -Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - var points; - - // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... - if (this.currentPath) - { - points = this.currentPath.shape.points; - - if (points.length === 0) - { - points.push(startX, startY); - } - else if (points[points.length-2] !== startX || points[points.length-1] !== startY) - { - points.push(startX, startY); - } - } - else - { - this.moveTo(startX, startY); - points = this.currentPath.shape.points; - } - - if (startAngle === endAngle) - { - return this; - } - - if (!anticlockwise && endAngle <= startAngle) - { - endAngle += Math.PI * 2; - } - else if (anticlockwise && startAngle <= endAngle) - { - startAngle += Math.PI * 2; - } - - var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); - var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; - - if (sweep === 0) - { - return this; - } - - var theta = sweep/(segs*2); - var theta2 = theta*2; - - var cTheta = Math.cos(theta); - var sTheta = Math.sin(theta); - - var segMinus = segs - 1; - - var remainder = ( segMinus % 1 ) / segMinus; - - for (var i = 0; i <= segMinus; ++i) - { - var real = i + remainder * i; - var angle = ((theta) + startAngle + (theta2 * real)); - - var c = Math.cos(angle); - var s = -Math.sin(angle); - - points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, - ( (cTheta * -s) + (sTheta * c) ) * radius + cy); - } - - this.dirty = true; - - return this; -}; - -/** - * Specifies a simple one-color fill that subsequent calls to other Graphics methods - * (such as lineTo() or drawCircle()) use when drawing. - * - * @param color {number} the color of the fill - * @param alpha {number} the alpha of the fill - * @return {Graphics} - */ -Graphics.prototype.beginFill = function (color, alpha) -{ - this.filling = true; - this.fillColor = color || 0; - this.fillAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length <= 2) - { - this.currentPath.fill = this.filling; - this.currentPath.fillColor = this.fillColor; - this.currentPath.fillAlpha = this.fillAlpha; - } - } - return this; -}; - -/** - * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. - * - * @return {Graphics} - */ -Graphics.prototype.endFill = function () -{ - this.filling = false; - this.fillColor = null; - this.fillAlpha = 1; - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @return {Graphics} - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {Graphics} - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {Graphics} - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {Array} The path data used to construct the polygon. - * @return {Graphics} - */ -Graphics.prototype.drawPolygon = function (path) -{ - if (!(path instanceof Array)) - { - path = Array.prototype.slice.call(arguments); - } - - this.drawShape(new math.Polygon(path)); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {Graphics} - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty = true; - this.clearDirty = true; - this.graphicsData = []; - - return this; -}; - -/** - * Useful function that returns a texture of the graphics object that can then be used to create sprites - * This can be quite useful if your geometry is complicated and needs to be reused multiple times. - * - * @param resolution {number} The resolution of the texture being generated - * @param scaleMode {number} Should be one of the scaleMode consts - * @return {Texture} a texture of the graphics object - */ -Graphics.prototype.generateTexture = function (resolution, scaleMode) -{ - resolution = resolution || 1; - - var bounds = this.getBounds(); - - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); - - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - canvasBuffer.context.scale(resolution, resolution); - - canvasBuffer.context.translate(-bounds.x,-bounds.y); - - CanvasGraphics.renderGraphics(this, canvasBuffer.context); - - return texture; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (this.isMask === true) - { - return; - } - - // this code may still be needed so leaving for now.. - // - /* - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture on the gpu too! - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.worldAlpha = this.worldAlpha; - - Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); - - return; - } - - */ - - if (this.glDirty) - { - this.dirty = true; - this.glDirty = false; - } - - renderer.setObjectRenderer(renderer.objectRenderers.graphics); - renderer.objectRenderers.graphics.render(this); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} - * @private - */ -Graphics.prototype.renderCanvas = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.alpha <= 0 || this.isMask === true) - { - return; - } - - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.alpha = this.alpha; - - Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); - - return; - } - else - { - var context = renderer.context; - var transform = this.worldTransform; - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - var resolution = renderer.resolution; - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } - } -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @return {Rectangle} the rectangular bounding area - */ -Graphics.prototype.getBounds = function (matrix) -{ - // return an empty object if the item is a mask! - if (this.isMask) - { - return math.Rectangle.EMPTY; - } - - if (this.dirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.dirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - return this._bounds; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; - - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; -}; - -/** - * Generates the cached sprite when the sprite has cacheAsBitmap = true - * - * @private - */ -Graphics.prototype._generateCachedSprite = function () -{ - var bounds = this.getLocalBounds(); - - if (!this._cachedSprite) - { - var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); - var texture = Texture.fromCanvas(canvasBuffer.canvas); - - this._cachedSprite = new Sprite(texture); - this._cachedSprite.buffer = canvasBuffer; - - this._cachedSprite.worldTransform = this.worldTransform; - } - else - { - this._cachedSprite.buffer.resize(bounds.width, bounds.height); - } - - // leverage the anchor to account for the offset of the element - this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); - this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); - - // this._cachedSprite.buffer.context.save(); - this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); - - // make sure we set the alpha of the graphics to 1 for the render.. - this.worldAlpha = 1; - - // now render the graphic.. - CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); - - this._cachedSprite.alpha = this.alpha; -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Graphics.prototype.updateCachedSpriteTexture = function () -{ - var cachedSprite = this._cachedSprite; - var texture = cachedSprite.texture; - var canvas = cachedSprite.buffer.canvas; - - texture.baseTexture.width = canvas.width; - texture.baseTexture.height = canvas.height; - texture.crop.width = texture.frame.width = canvas.width; - texture.crop.height = texture.frame.height = canvas.height; - - cachedSprite._width = canvas.width; - cachedSprite._height = canvas.height; - - // update the dirty base textures - texture.baseTexture.dirty(); -}; - -/** - * Destroys a previous cached sprite. - * - */ -Graphics.prototype.destroyCachedSprite = function () -{ - this._cachedSprite.texture.destroy(true); - - // let the gc collect the unused sprite - // TODO could be object pooled! - this._cachedSprite = null; -}; - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. - * @return {GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = this.filling; - this.currentPath = data; - } - - this.dirty = true; - - return data; -}; diff --git a/src/core/primitives/GraphicsData.js b/src/core/primitives/GraphicsData.js deleted file mode 100644 index 788f6f2..0000000 --- a/src/core/primitives/GraphicsData.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * A GraphicsData object. - * - * @class - * @namespace PIXI - */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - this.lineWidth = lineWidth; - this.lineColor = lineColor; - this.lineAlpha = lineAlpha; - this._lineTint = lineColor; - - this.fillColor = fillColor; - this.fillAlpha = fillAlpha; - this._fillTint = fillColor; - this.fill = fill; - - this.shape = shape; - this.type = shape.type; -} - -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {GraphicsData} - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js deleted file mode 100644 index 1a508f9..0000000 --- a/src/core/primitives/GraphicsRenderer.js +++ /dev/null @@ -1,874 +0,0 @@ -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; - -/** - * 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 ); - -// var matrix = graphics.worldTransform.clone(); -// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); -// matrix.append(graphics.worldTransform); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.maskManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.maskManager.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.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); - - 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/primitives/WebGLGraphicsData.js b/src/core/primitives/WebGLGraphicsData.js deleted file mode 100755 index 41be102..0000000 --- a/src/core/primitives/WebGLGraphicsData.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @class - * @private - */ -function WebGLGraphicsData(gl) { - this.gl = gl; - - //TODO does this need to be split before uploding?? - this.color = [0,0,0]; // color split! - this.points = []; - this.indices = []; - this.buffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - this.mode = 1; - this.alpha = 1; - this.dirty = true; -} - -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; -module.exports = WebGLGraphicsData; - -/** - * - */ -WebGLGraphicsData.prototype.reset = function () { - this.points = []; - this.indices = []; -}; - -/** - * - */ -WebGLGraphicsData.prototype.upload = function () { - var gl = this.gl; - -// this.lastIndex = graphics.graphicsData.length; - this.glPoints = new Float32Array(this.points); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); - - this.glIndicies = new Uint16Array(this.indices); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); - - this.dirty = false; -}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6c1fb7d..4e82baa 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -174,6 +174,9 @@ // time to create the render managers! each one focuses on managing a state in webGL + // initialize the context so it is ready for the managers. + this._initContext(); + /** * Deals with managing the shader programs and their attribs * @member {WebGLShaderManager} @@ -204,17 +207,11 @@ */ this.blendModeManager = new WebGLBlendModeManager(this); - - this.blendModes = null; this._boundUpdateTexture = this.updateTexture.bind(this); this._boundDestroyTexture = this.destroyTexture.bind(this); - - - // time init the context.. - this._initContext(); this.currentRenderTarget = this.renderTarget; @@ -228,27 +225,18 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - /** - * Manages the renderer of specific objects. - * - * @member {object} - */ - this.objectRenderers = {}; - this.currentRenderer = new ObjectRenderer(); - - // create an instance of each registered object renderer - for (var o in WebGLRenderer._objectRenderers) { - this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); - } - + this.initPlugins(); } // constructor WebGLRenderer.prototype.constructor = WebGLRenderer; module.exports = WebGLRenderer; +WebGLRenderer.glContextId = 0; + +utils.pluginTarget.mixin(WebGLRenderer); utils.eventTarget.mixin(WebGLRenderer.prototype); Object.defineProperties(WebGLRenderer.prototype, { @@ -297,11 +285,9 @@ gl.enable(gl.BLEND); this.renderTarget = new RenderTarget(this.gl, this.width, this.height, null, true); - this.emit('context', gl); - // now resize and we are good to go! this.resize(this.width, this.height); }; @@ -349,7 +335,7 @@ gl.clear(gl.COLOR_BUFFER_BIT); } - this.renderDisplayObject(object, this.renderTarget)//this.projection); + this.renderDisplayObject(object, this.renderTarget);//this.projection); }; /** @@ -536,12 +522,7 @@ this.maskManager.destroy(); this.filterManager.destroy(); - for (var o in this.objectRenderers) { - this.objectRenderers[o].destroy(); - this.objectRenderers[o] = null; - } - - this.objectRenderers = null; + this.destroyPlugins(); // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -611,11 +592,3 @@ this.blendModes[CONST.blendModes.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; } }; - -WebGLRenderer.glContextId = 0; -WebGLRenderer._objectRenderers = {}; - -WebGLRenderer.registerObjectRenderer = function (name, ctor) { - WebGLRenderer._objectRenderers[name] = ctor; -}; - diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js index 87c99f0..d6ed3c6 100644 --- a/src/core/renderers/webgl/managers/WebGLFilterManager.js +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -51,7 +51,7 @@ */ WebGLFilterManager.prototype.begin = function (buffer) { - this.defaultShader = this.renderer.shaderManager.defaultShader; + this.defaultShader = this.renderer.shaderManager.plugins.defaultShader; this.width = this.renderer.projection.x * 2; this.height = -this.renderer.projection.y * 2; @@ -385,7 +385,7 @@ if (!shader) { - shader = new Shader(gl); + shader = new Shader(this.renderer.shaderManager); shader.fragmentSrc = filter.fragmentSrc; shader.uniforms = filter.uniforms; diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js new file mode 100644 index 0000000..41be102 --- /dev/null +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -0,0 +1,48 @@ +/** + * @class + * @private + */ +function WebGLGraphicsData(gl) { + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +} + +WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; +module.exports = WebGLGraphicsData; + +/** + * + */ +WebGLGraphicsData.prototype.reset = function () { + this.points = []; + this.indices = []; +}; + +/** + * + */ +WebGLGraphicsData.prototype.upload = function () { + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; diff --git a/src/core/index.js b/src/core/index.js index 6f744e9..1d0a398 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -23,12 +23,12 @@ Sprite: require('./sprites/Sprite'), SpriteBatch: require('./sprites/SpriteBatch'), - SpriteRenderer: require('./sprites/SpriteRenderer'), + SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), // primitives - Graphics: require('./primitives/Graphics'), - GraphicsData: require('./primitives/GraphicsData'), - GraphicsRenderer: require('./primitives/GraphicsRenderer'), + Graphics: require('./graphics/Graphics'), + GraphicsData: require('./graphics/GraphicsData'), + GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,10 +42,8 @@ CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), // renderers - webgl - WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), - // WebGLGraphics: require('./renderers/webgl/utils/WebGLGraphics'), - - + WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), + Shader: require('./renderers/webgl/shaders/Shader'), /** * This helper function will automatically detect which renderer you should be using. @@ -103,7 +101,3 @@ return core.autoDetectRenderer(width, height, options, isAndroid); } }; - -// Adding here for now.. sure there is a better place! -core.WebGLRenderer.registerObjectRenderer('sprite', core.SpriteRenderer); -core.WebGLRenderer.registerObjectRenderer('graphics', core.GraphicsRenderer); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index a086950..81de05c 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -268,6 +268,6 @@ matrix.ty = this.ty; return matrix; -} +}; Matrix.IDENTITY = new Matrix(); diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js deleted file mode 100644 index 8db9b45..0000000 --- a/src/core/primitives/Graphics.js +++ /dev/null @@ -1,1116 +0,0 @@ -var DisplayObjectContainer = require('../display/DisplayObjectContainer'), - //Sprite = require('../display/Sprite'), - Texture = require('../textures/Texture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), - // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), - GraphicsData = require('./GraphicsData'), - math = require('../math'), - CONST = require('../const'); - -/** - * The Graphics class contains methods used to draw primitive shapes such as lines, circles and - * rectangles to the display, and color and fill them. - * - * @class - * @extends DisplayObjectContainer - * @namespace PIXI - */ -function Graphics() -{ - DisplayObjectContainer.call(this); - - this.renderable = true; - - /** - * The alpha value used when filling the Graphics object. - * - * @member {number} - * @default 1 - */ - this.fillAlpha = 1; - - /** - * The width (thickness) of any lines drawn. - * - * @member {number} - * @default 0 - */ - this.lineWidth = 0; - - /** - * The color of any lines drawn. - * - * @member {string} - * @default 0 - */ - this.lineColor = 0; - - /** - * Graphics data - * - * @member {GraphicsData[]} - * @private - */ - this.graphicsData = []; - - /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * Current path - * - * @member {GraphicsData} - * @private - */ - this.currentPath = null; - - /** - * Array containing some WebGL-related properties used by the WebGL renderer. - * - * @member {object} - * @private - */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; - - /** - * Whether this shape is being used as a mask. - * - * @member {boolean} - */ - this.isMask = false; - - /** - * The bounds' padding used for bounds calculation. - * - * @member {number} - */ - this.boundsPadding = 0; - - /** - * A cache of the local bounds to prevent recalculation. - * - * @member {Rectangle} - * @private - */ - this._localBounds = new math.Rectangle(0,0,1,1); - - /** - * Used to detect if the graphics object has changed. If this is set to true then the graphics - * object will be recalculated. - * - * @member {boolean} - * @private - */ - this.dirty = true; - - /** - * Used to detect if the WebGL graphics object has changed. If this is set to true then the - * graphics object will be recalculated. - * - * @member {boolean} - * @private - */ - this.glDirty = false; - - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; -} - -// constructor -Graphics.prototype = Object.create(DisplayObjectContainer.prototype); -Graphics.prototype.constructor = Graphics; -module.exports = Graphics; - -Object.defineProperties(Graphics.prototype, { - /** - * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. - * This is useful if your graphics element does not change often, as it will speed up the rendering - * of the object in exchange for taking up texture memory. It is also useful if you need the graphics - * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if - * you are constantly redrawing the graphics element. - * - * @member {boolean} - * @memberof Graphics# - * @default false - * @private - */ - cacheAsBitmap: { - get: function () - { - return this._cacheAsBitmap; - }, - set: function (value) - { - this._cacheAsBitmap = value; - - if (this._cacheAsBitmap) - { - this._generateCachedSprite(); - } - else - { - this.destroyCachedSprite(); - this.dirty = true; - } - } - } -}); - -/** - * Creates a new Graphics object with the same values as this one. - * - * @return {Graphics} - */ -GraphicsData.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = this.dirty; - clone.glDirty = this.glDirty; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData.clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {Graphics} - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (arguments.length < 3) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); - } - else - { - // otherwise its empty so lets just set the line properties - this.currentPath.lineWidth = this.lineWidth; - this.currentPath.lineColor = this.lineColor; - this.currentPath.lineAlpha = this.lineAlpha; - } - } - - return this; -}; - -/** - * Moves the current drawing position to x, y. - * - * @param x {number} the X coordinate to move to - * @param y {number} the Y coordinate to move to - * @return {Graphics} - */ -Graphics.prototype.moveTo = function (x, y) -{ - this.drawShape(new math.Polygon([x,y])); - - return this; -}; - -/** - * Draws a line using the current line style from the current drawing position to (x, y); - * The current drawing position is then set to (x, y). - * - * @param x {number} the X coordinate to draw to - * @param y {number} the Y coordinate to draw to - * @return {Graphics} - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a quadratic bezier curve and then draws it. - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @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 {Graphics} - */ -Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var xa, - ya, - n = 20, - points = this.currentPath.shape.points; - - if (points.length === 0) - { - this.moveTo(0, 0); - } - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - for (var i = 1; i <= n; ++i) - { - j = i / n; - - xa = fromX + ( (cpX - fromX) * j ); - ya = fromY + ( (cpY - fromY) * j ); - - points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), - ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); - } - - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a bezier curve and then draws it. - * - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param cpX2 {number} Second Control point x - * @param cpY2 {number} Second Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {Graphics} - */ -Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var n = 20, - dt, - dt2, - dt3, - t2, - t3, - points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - - for (var i = 1; i <= n; ++i) - { - j = i / n; - - dt = (1 - j); - dt2 = dt * dt; - dt3 = dt2 * dt; - - t2 = j * j; - t3 = t2 * j; - - points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, - dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); - } - - this.dirty = true; - - return this; -}; - -/** - * The arcTo() method creates an arc/curve between two tangents on the canvas. - * - * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! - * - * @param x1 {number} The x-coordinate of the beginning of the arc - * @param y1 {number} The y-coordinate of the beginning of the arc - * @param x2 {number} The x-coordinate of the end of the arc - * @param y2 {number} The y-coordinate of the end of the arc - * @param radius {number} The radius of the arc - * @return {Graphics} - */ -Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points.push(x1, y1); - } - } - else - { - this.moveTo(x1, y1); - } - - var points = this.currentPath.shape.points, - fromX = points[points.length-2], - fromY = points[points.length-1], - a1 = fromY - y1, - b1 = fromX - x1, - a2 = y2 - y1, - b2 = x2 - x1, - mm = Math.abs(a1 * b2 - b1 * a2); - - if (mm < 1.0e-8 || radius === 0) - { - if (points[points.length-2] !== x1 || points[points.length-1] !== y1) - { - points.push(x1, y1); - } - } - else - { - var dd = a1 * a1 + b1 * b1, - cc = a2 * a2 + b2 * b2, - tt = a1 * a2 + b1 * b2, - k1 = radius * Math.sqrt(dd) / mm, - k2 = radius * Math.sqrt(cc) / mm, - j1 = k1 * tt / dd, - j2 = k2 * tt / cc, - cx = k1 * b2 + k2 * b1, - cy = k1 * a2 + k2 * a1, - px = b1 * (k2 + j1), - py = a1 * (k2 + j1), - qx = b2 * (k1 + j2), - qy = a2 * (k1 + j2), - startAngle = Math.atan2(py - cy, px - cx), - endAngle = Math.atan2(qy - cy, qx - cx); - - this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); - } - - this.dirty = true; - - return this; -}; - -/** - * The arc method creates an arc/curve (used to create circles, or parts of circles). - * - * @param cx {number} The x-coordinate of the center of the circle - * @param cy {number} The y-coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) - * @param endAngle {number} The ending angle, in radians - * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {Graphics} - */ -Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - var points; - - // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... - if (this.currentPath) - { - points = this.currentPath.shape.points; - - if (points.length === 0) - { - points.push(startX, startY); - } - else if (points[points.length-2] !== startX || points[points.length-1] !== startY) - { - points.push(startX, startY); - } - } - else - { - this.moveTo(startX, startY); - points = this.currentPath.shape.points; - } - - if (startAngle === endAngle) - { - return this; - } - - if (!anticlockwise && endAngle <= startAngle) - { - endAngle += Math.PI * 2; - } - else if (anticlockwise && startAngle <= endAngle) - { - startAngle += Math.PI * 2; - } - - var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); - var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; - - if (sweep === 0) - { - return this; - } - - var theta = sweep/(segs*2); - var theta2 = theta*2; - - var cTheta = Math.cos(theta); - var sTheta = Math.sin(theta); - - var segMinus = segs - 1; - - var remainder = ( segMinus % 1 ) / segMinus; - - for (var i = 0; i <= segMinus; ++i) - { - var real = i + remainder * i; - var angle = ((theta) + startAngle + (theta2 * real)); - - var c = Math.cos(angle); - var s = -Math.sin(angle); - - points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, - ( (cTheta * -s) + (sTheta * c) ) * radius + cy); - } - - this.dirty = true; - - return this; -}; - -/** - * Specifies a simple one-color fill that subsequent calls to other Graphics methods - * (such as lineTo() or drawCircle()) use when drawing. - * - * @param color {number} the color of the fill - * @param alpha {number} the alpha of the fill - * @return {Graphics} - */ -Graphics.prototype.beginFill = function (color, alpha) -{ - this.filling = true; - this.fillColor = color || 0; - this.fillAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length <= 2) - { - this.currentPath.fill = this.filling; - this.currentPath.fillColor = this.fillColor; - this.currentPath.fillAlpha = this.fillAlpha; - } - } - return this; -}; - -/** - * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. - * - * @return {Graphics} - */ -Graphics.prototype.endFill = function () -{ - this.filling = false; - this.fillColor = null; - this.fillAlpha = 1; - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @return {Graphics} - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {Graphics} - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {Graphics} - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {Array} The path data used to construct the polygon. - * @return {Graphics} - */ -Graphics.prototype.drawPolygon = function (path) -{ - if (!(path instanceof Array)) - { - path = Array.prototype.slice.call(arguments); - } - - this.drawShape(new math.Polygon(path)); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {Graphics} - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty = true; - this.clearDirty = true; - this.graphicsData = []; - - return this; -}; - -/** - * Useful function that returns a texture of the graphics object that can then be used to create sprites - * This can be quite useful if your geometry is complicated and needs to be reused multiple times. - * - * @param resolution {number} The resolution of the texture being generated - * @param scaleMode {number} Should be one of the scaleMode consts - * @return {Texture} a texture of the graphics object - */ -Graphics.prototype.generateTexture = function (resolution, scaleMode) -{ - resolution = resolution || 1; - - var bounds = this.getBounds(); - - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); - - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - canvasBuffer.context.scale(resolution, resolution); - - canvasBuffer.context.translate(-bounds.x,-bounds.y); - - CanvasGraphics.renderGraphics(this, canvasBuffer.context); - - return texture; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (this.isMask === true) - { - return; - } - - // this code may still be needed so leaving for now.. - // - /* - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture on the gpu too! - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.worldAlpha = this.worldAlpha; - - Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); - - return; - } - - */ - - if (this.glDirty) - { - this.dirty = true; - this.glDirty = false; - } - - renderer.setObjectRenderer(renderer.objectRenderers.graphics); - renderer.objectRenderers.graphics.render(this); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} - * @private - */ -Graphics.prototype.renderCanvas = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.alpha <= 0 || this.isMask === true) - { - return; - } - - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.alpha = this.alpha; - - Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); - - return; - } - else - { - var context = renderer.context; - var transform = this.worldTransform; - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - var resolution = renderer.resolution; - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } - } -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @return {Rectangle} the rectangular bounding area - */ -Graphics.prototype.getBounds = function (matrix) -{ - // return an empty object if the item is a mask! - if (this.isMask) - { - return math.Rectangle.EMPTY; - } - - if (this.dirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.dirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - return this._bounds; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; - - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; -}; - -/** - * Generates the cached sprite when the sprite has cacheAsBitmap = true - * - * @private - */ -Graphics.prototype._generateCachedSprite = function () -{ - var bounds = this.getLocalBounds(); - - if (!this._cachedSprite) - { - var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); - var texture = Texture.fromCanvas(canvasBuffer.canvas); - - this._cachedSprite = new Sprite(texture); - this._cachedSprite.buffer = canvasBuffer; - - this._cachedSprite.worldTransform = this.worldTransform; - } - else - { - this._cachedSprite.buffer.resize(bounds.width, bounds.height); - } - - // leverage the anchor to account for the offset of the element - this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); - this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); - - // this._cachedSprite.buffer.context.save(); - this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); - - // make sure we set the alpha of the graphics to 1 for the render.. - this.worldAlpha = 1; - - // now render the graphic.. - CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); - - this._cachedSprite.alpha = this.alpha; -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Graphics.prototype.updateCachedSpriteTexture = function () -{ - var cachedSprite = this._cachedSprite; - var texture = cachedSprite.texture; - var canvas = cachedSprite.buffer.canvas; - - texture.baseTexture.width = canvas.width; - texture.baseTexture.height = canvas.height; - texture.crop.width = texture.frame.width = canvas.width; - texture.crop.height = texture.frame.height = canvas.height; - - cachedSprite._width = canvas.width; - cachedSprite._height = canvas.height; - - // update the dirty base textures - texture.baseTexture.dirty(); -}; - -/** - * Destroys a previous cached sprite. - * - */ -Graphics.prototype.destroyCachedSprite = function () -{ - this._cachedSprite.texture.destroy(true); - - // let the gc collect the unused sprite - // TODO could be object pooled! - this._cachedSprite = null; -}; - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. - * @return {GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = this.filling; - this.currentPath = data; - } - - this.dirty = true; - - return data; -}; diff --git a/src/core/primitives/GraphicsData.js b/src/core/primitives/GraphicsData.js deleted file mode 100644 index 788f6f2..0000000 --- a/src/core/primitives/GraphicsData.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * A GraphicsData object. - * - * @class - * @namespace PIXI - */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - this.lineWidth = lineWidth; - this.lineColor = lineColor; - this.lineAlpha = lineAlpha; - this._lineTint = lineColor; - - this.fillColor = fillColor; - this.fillAlpha = fillAlpha; - this._fillTint = fillColor; - this.fill = fill; - - this.shape = shape; - this.type = shape.type; -} - -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {GraphicsData} - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js deleted file mode 100644 index 1a508f9..0000000 --- a/src/core/primitives/GraphicsRenderer.js +++ /dev/null @@ -1,874 +0,0 @@ -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; - -/** - * 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 ); - -// var matrix = graphics.worldTransform.clone(); -// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); -// matrix.append(graphics.worldTransform); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.maskManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.maskManager.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.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); - - 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/primitives/WebGLGraphicsData.js b/src/core/primitives/WebGLGraphicsData.js deleted file mode 100755 index 41be102..0000000 --- a/src/core/primitives/WebGLGraphicsData.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @class - * @private - */ -function WebGLGraphicsData(gl) { - this.gl = gl; - - //TODO does this need to be split before uploding?? - this.color = [0,0,0]; // color split! - this.points = []; - this.indices = []; - this.buffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - this.mode = 1; - this.alpha = 1; - this.dirty = true; -} - -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; -module.exports = WebGLGraphicsData; - -/** - * - */ -WebGLGraphicsData.prototype.reset = function () { - this.points = []; - this.indices = []; -}; - -/** - * - */ -WebGLGraphicsData.prototype.upload = function () { - var gl = this.gl; - -// this.lastIndex = graphics.graphicsData.length; - this.glPoints = new Float32Array(this.points); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); - - this.glIndicies = new Uint16Array(this.indices); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); - - this.dirty = false; -}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6c1fb7d..4e82baa 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -174,6 +174,9 @@ // time to create the render managers! each one focuses on managing a state in webGL + // initialize the context so it is ready for the managers. + this._initContext(); + /** * Deals with managing the shader programs and their attribs * @member {WebGLShaderManager} @@ -204,17 +207,11 @@ */ this.blendModeManager = new WebGLBlendModeManager(this); - - this.blendModes = null; this._boundUpdateTexture = this.updateTexture.bind(this); this._boundDestroyTexture = this.destroyTexture.bind(this); - - - // time init the context.. - this._initContext(); this.currentRenderTarget = this.renderTarget; @@ -228,27 +225,18 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - /** - * Manages the renderer of specific objects. - * - * @member {object} - */ - this.objectRenderers = {}; - this.currentRenderer = new ObjectRenderer(); - - // create an instance of each registered object renderer - for (var o in WebGLRenderer._objectRenderers) { - this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); - } - + this.initPlugins(); } // constructor WebGLRenderer.prototype.constructor = WebGLRenderer; module.exports = WebGLRenderer; +WebGLRenderer.glContextId = 0; + +utils.pluginTarget.mixin(WebGLRenderer); utils.eventTarget.mixin(WebGLRenderer.prototype); Object.defineProperties(WebGLRenderer.prototype, { @@ -297,11 +285,9 @@ gl.enable(gl.BLEND); this.renderTarget = new RenderTarget(this.gl, this.width, this.height, null, true); - this.emit('context', gl); - // now resize and we are good to go! this.resize(this.width, this.height); }; @@ -349,7 +335,7 @@ gl.clear(gl.COLOR_BUFFER_BIT); } - this.renderDisplayObject(object, this.renderTarget)//this.projection); + this.renderDisplayObject(object, this.renderTarget);//this.projection); }; /** @@ -536,12 +522,7 @@ this.maskManager.destroy(); this.filterManager.destroy(); - for (var o in this.objectRenderers) { - this.objectRenderers[o].destroy(); - this.objectRenderers[o] = null; - } - - this.objectRenderers = null; + this.destroyPlugins(); // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -611,11 +592,3 @@ this.blendModes[CONST.blendModes.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; } }; - -WebGLRenderer.glContextId = 0; -WebGLRenderer._objectRenderers = {}; - -WebGLRenderer.registerObjectRenderer = function (name, ctor) { - WebGLRenderer._objectRenderers[name] = ctor; -}; - diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js index 87c99f0..d6ed3c6 100644 --- a/src/core/renderers/webgl/managers/WebGLFilterManager.js +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -51,7 +51,7 @@ */ WebGLFilterManager.prototype.begin = function (buffer) { - this.defaultShader = this.renderer.shaderManager.defaultShader; + this.defaultShader = this.renderer.shaderManager.plugins.defaultShader; this.width = this.renderer.projection.x * 2; this.height = -this.renderer.projection.y * 2; @@ -385,7 +385,7 @@ if (!shader) { - shader = new Shader(gl); + shader = new Shader(this.renderer.shaderManager); shader.fragmentSrc = filter.fragmentSrc; shader.uniforms = filter.uniforms; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js index 6a1f782..d6a11c1 100644 --- a/src/core/renderers/webgl/managers/WebGLMaskManager.js +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -126,11 +126,11 @@ // bind the graphics object.. var projection = this.renderer.projection, offset = this.renderer.offset, - shader;// = this.renderer.shaderManager.primitiveShader; + shader;// = this.renderer.shaderManager.plugins.primitiveShader; if (webGLData.mode === 1) { - shader = this.renderer.shaderManager.complexPrimitiveShader; + shader = this.renderer.shaderManager.plugins.complexPrimitiveShader; this.renderer.shaderManager.setShader(shader); @@ -156,7 +156,7 @@ else { //this.renderer.shaderManager.activatePrimitiveShader(); - shader = this.renderer.shaderManager.primitiveShader; + shader = this.renderer.shaderManager.plugins.primitiveShader; this.renderer.shaderManager.setShader( shader ); gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); @@ -292,11 +292,11 @@ */ WebGLMaskManager.prototype.pushMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); if (maskData.dirty) { - this.renderer.objectRenderers.graphics.updateGraphics(maskData, this.renderer.gl); + this.renderer.plugins.graphics.updateGraphics(maskData, this.renderer.gl); } if (!maskData._webGL[this.renderer.gl.id].data.length) @@ -314,7 +314,7 @@ */ WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); this.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 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js new file mode 100644 index 0000000..41be102 --- /dev/null +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -0,0 +1,48 @@ +/** + * @class + * @private + */ +function WebGLGraphicsData(gl) { + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +} + +WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; +module.exports = WebGLGraphicsData; + +/** + * + */ +WebGLGraphicsData.prototype.reset = function () { + this.points = []; + this.indices = []; +}; + +/** + * + */ +WebGLGraphicsData.prototype.upload = function () { + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; diff --git a/src/core/index.js b/src/core/index.js index 6f744e9..1d0a398 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -23,12 +23,12 @@ Sprite: require('./sprites/Sprite'), SpriteBatch: require('./sprites/SpriteBatch'), - SpriteRenderer: require('./sprites/SpriteRenderer'), + SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), // primitives - Graphics: require('./primitives/Graphics'), - GraphicsData: require('./primitives/GraphicsData'), - GraphicsRenderer: require('./primitives/GraphicsRenderer'), + Graphics: require('./graphics/Graphics'), + GraphicsData: require('./graphics/GraphicsData'), + GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,10 +42,8 @@ CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), // renderers - webgl - WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), - // WebGLGraphics: require('./renderers/webgl/utils/WebGLGraphics'), - - + WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), + Shader: require('./renderers/webgl/shaders/Shader'), /** * This helper function will automatically detect which renderer you should be using. @@ -103,7 +101,3 @@ return core.autoDetectRenderer(width, height, options, isAndroid); } }; - -// Adding here for now.. sure there is a better place! -core.WebGLRenderer.registerObjectRenderer('sprite', core.SpriteRenderer); -core.WebGLRenderer.registerObjectRenderer('graphics', core.GraphicsRenderer); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index a086950..81de05c 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -268,6 +268,6 @@ matrix.ty = this.ty; return matrix; -} +}; Matrix.IDENTITY = new Matrix(); diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js deleted file mode 100644 index 8db9b45..0000000 --- a/src/core/primitives/Graphics.js +++ /dev/null @@ -1,1116 +0,0 @@ -var DisplayObjectContainer = require('../display/DisplayObjectContainer'), - //Sprite = require('../display/Sprite'), - Texture = require('../textures/Texture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), - // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), - GraphicsData = require('./GraphicsData'), - math = require('../math'), - CONST = require('../const'); - -/** - * The Graphics class contains methods used to draw primitive shapes such as lines, circles and - * rectangles to the display, and color and fill them. - * - * @class - * @extends DisplayObjectContainer - * @namespace PIXI - */ -function Graphics() -{ - DisplayObjectContainer.call(this); - - this.renderable = true; - - /** - * The alpha value used when filling the Graphics object. - * - * @member {number} - * @default 1 - */ - this.fillAlpha = 1; - - /** - * The width (thickness) of any lines drawn. - * - * @member {number} - * @default 0 - */ - this.lineWidth = 0; - - /** - * The color of any lines drawn. - * - * @member {string} - * @default 0 - */ - this.lineColor = 0; - - /** - * Graphics data - * - * @member {GraphicsData[]} - * @private - */ - this.graphicsData = []; - - /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * Current path - * - * @member {GraphicsData} - * @private - */ - this.currentPath = null; - - /** - * Array containing some WebGL-related properties used by the WebGL renderer. - * - * @member {object} - * @private - */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; - - /** - * Whether this shape is being used as a mask. - * - * @member {boolean} - */ - this.isMask = false; - - /** - * The bounds' padding used for bounds calculation. - * - * @member {number} - */ - this.boundsPadding = 0; - - /** - * A cache of the local bounds to prevent recalculation. - * - * @member {Rectangle} - * @private - */ - this._localBounds = new math.Rectangle(0,0,1,1); - - /** - * Used to detect if the graphics object has changed. If this is set to true then the graphics - * object will be recalculated. - * - * @member {boolean} - * @private - */ - this.dirty = true; - - /** - * Used to detect if the WebGL graphics object has changed. If this is set to true then the - * graphics object will be recalculated. - * - * @member {boolean} - * @private - */ - this.glDirty = false; - - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; -} - -// constructor -Graphics.prototype = Object.create(DisplayObjectContainer.prototype); -Graphics.prototype.constructor = Graphics; -module.exports = Graphics; - -Object.defineProperties(Graphics.prototype, { - /** - * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. - * This is useful if your graphics element does not change often, as it will speed up the rendering - * of the object in exchange for taking up texture memory. It is also useful if you need the graphics - * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if - * you are constantly redrawing the graphics element. - * - * @member {boolean} - * @memberof Graphics# - * @default false - * @private - */ - cacheAsBitmap: { - get: function () - { - return this._cacheAsBitmap; - }, - set: function (value) - { - this._cacheAsBitmap = value; - - if (this._cacheAsBitmap) - { - this._generateCachedSprite(); - } - else - { - this.destroyCachedSprite(); - this.dirty = true; - } - } - } -}); - -/** - * Creates a new Graphics object with the same values as this one. - * - * @return {Graphics} - */ -GraphicsData.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = this.dirty; - clone.glDirty = this.glDirty; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData.clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {Graphics} - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (arguments.length < 3) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); - } - else - { - // otherwise its empty so lets just set the line properties - this.currentPath.lineWidth = this.lineWidth; - this.currentPath.lineColor = this.lineColor; - this.currentPath.lineAlpha = this.lineAlpha; - } - } - - return this; -}; - -/** - * Moves the current drawing position to x, y. - * - * @param x {number} the X coordinate to move to - * @param y {number} the Y coordinate to move to - * @return {Graphics} - */ -Graphics.prototype.moveTo = function (x, y) -{ - this.drawShape(new math.Polygon([x,y])); - - return this; -}; - -/** - * Draws a line using the current line style from the current drawing position to (x, y); - * The current drawing position is then set to (x, y). - * - * @param x {number} the X coordinate to draw to - * @param y {number} the Y coordinate to draw to - * @return {Graphics} - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a quadratic bezier curve and then draws it. - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @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 {Graphics} - */ -Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var xa, - ya, - n = 20, - points = this.currentPath.shape.points; - - if (points.length === 0) - { - this.moveTo(0, 0); - } - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - for (var i = 1; i <= n; ++i) - { - j = i / n; - - xa = fromX + ( (cpX - fromX) * j ); - ya = fromY + ( (cpY - fromY) * j ); - - points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), - ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); - } - - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a bezier curve and then draws it. - * - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param cpX2 {number} Second Control point x - * @param cpY2 {number} Second Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {Graphics} - */ -Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var n = 20, - dt, - dt2, - dt3, - t2, - t3, - points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - - for (var i = 1; i <= n; ++i) - { - j = i / n; - - dt = (1 - j); - dt2 = dt * dt; - dt3 = dt2 * dt; - - t2 = j * j; - t3 = t2 * j; - - points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, - dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); - } - - this.dirty = true; - - return this; -}; - -/** - * The arcTo() method creates an arc/curve between two tangents on the canvas. - * - * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! - * - * @param x1 {number} The x-coordinate of the beginning of the arc - * @param y1 {number} The y-coordinate of the beginning of the arc - * @param x2 {number} The x-coordinate of the end of the arc - * @param y2 {number} The y-coordinate of the end of the arc - * @param radius {number} The radius of the arc - * @return {Graphics} - */ -Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points.push(x1, y1); - } - } - else - { - this.moveTo(x1, y1); - } - - var points = this.currentPath.shape.points, - fromX = points[points.length-2], - fromY = points[points.length-1], - a1 = fromY - y1, - b1 = fromX - x1, - a2 = y2 - y1, - b2 = x2 - x1, - mm = Math.abs(a1 * b2 - b1 * a2); - - if (mm < 1.0e-8 || radius === 0) - { - if (points[points.length-2] !== x1 || points[points.length-1] !== y1) - { - points.push(x1, y1); - } - } - else - { - var dd = a1 * a1 + b1 * b1, - cc = a2 * a2 + b2 * b2, - tt = a1 * a2 + b1 * b2, - k1 = radius * Math.sqrt(dd) / mm, - k2 = radius * Math.sqrt(cc) / mm, - j1 = k1 * tt / dd, - j2 = k2 * tt / cc, - cx = k1 * b2 + k2 * b1, - cy = k1 * a2 + k2 * a1, - px = b1 * (k2 + j1), - py = a1 * (k2 + j1), - qx = b2 * (k1 + j2), - qy = a2 * (k1 + j2), - startAngle = Math.atan2(py - cy, px - cx), - endAngle = Math.atan2(qy - cy, qx - cx); - - this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); - } - - this.dirty = true; - - return this; -}; - -/** - * The arc method creates an arc/curve (used to create circles, or parts of circles). - * - * @param cx {number} The x-coordinate of the center of the circle - * @param cy {number} The y-coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) - * @param endAngle {number} The ending angle, in radians - * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {Graphics} - */ -Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - var points; - - // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... - if (this.currentPath) - { - points = this.currentPath.shape.points; - - if (points.length === 0) - { - points.push(startX, startY); - } - else if (points[points.length-2] !== startX || points[points.length-1] !== startY) - { - points.push(startX, startY); - } - } - else - { - this.moveTo(startX, startY); - points = this.currentPath.shape.points; - } - - if (startAngle === endAngle) - { - return this; - } - - if (!anticlockwise && endAngle <= startAngle) - { - endAngle += Math.PI * 2; - } - else if (anticlockwise && startAngle <= endAngle) - { - startAngle += Math.PI * 2; - } - - var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); - var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; - - if (sweep === 0) - { - return this; - } - - var theta = sweep/(segs*2); - var theta2 = theta*2; - - var cTheta = Math.cos(theta); - var sTheta = Math.sin(theta); - - var segMinus = segs - 1; - - var remainder = ( segMinus % 1 ) / segMinus; - - for (var i = 0; i <= segMinus; ++i) - { - var real = i + remainder * i; - var angle = ((theta) + startAngle + (theta2 * real)); - - var c = Math.cos(angle); - var s = -Math.sin(angle); - - points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, - ( (cTheta * -s) + (sTheta * c) ) * radius + cy); - } - - this.dirty = true; - - return this; -}; - -/** - * Specifies a simple one-color fill that subsequent calls to other Graphics methods - * (such as lineTo() or drawCircle()) use when drawing. - * - * @param color {number} the color of the fill - * @param alpha {number} the alpha of the fill - * @return {Graphics} - */ -Graphics.prototype.beginFill = function (color, alpha) -{ - this.filling = true; - this.fillColor = color || 0; - this.fillAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length <= 2) - { - this.currentPath.fill = this.filling; - this.currentPath.fillColor = this.fillColor; - this.currentPath.fillAlpha = this.fillAlpha; - } - } - return this; -}; - -/** - * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. - * - * @return {Graphics} - */ -Graphics.prototype.endFill = function () -{ - this.filling = false; - this.fillColor = null; - this.fillAlpha = 1; - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @return {Graphics} - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {Graphics} - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {Graphics} - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {Array} The path data used to construct the polygon. - * @return {Graphics} - */ -Graphics.prototype.drawPolygon = function (path) -{ - if (!(path instanceof Array)) - { - path = Array.prototype.slice.call(arguments); - } - - this.drawShape(new math.Polygon(path)); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {Graphics} - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty = true; - this.clearDirty = true; - this.graphicsData = []; - - return this; -}; - -/** - * Useful function that returns a texture of the graphics object that can then be used to create sprites - * This can be quite useful if your geometry is complicated and needs to be reused multiple times. - * - * @param resolution {number} The resolution of the texture being generated - * @param scaleMode {number} Should be one of the scaleMode consts - * @return {Texture} a texture of the graphics object - */ -Graphics.prototype.generateTexture = function (resolution, scaleMode) -{ - resolution = resolution || 1; - - var bounds = this.getBounds(); - - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); - - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - canvasBuffer.context.scale(resolution, resolution); - - canvasBuffer.context.translate(-bounds.x,-bounds.y); - - CanvasGraphics.renderGraphics(this, canvasBuffer.context); - - return texture; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (this.isMask === true) - { - return; - } - - // this code may still be needed so leaving for now.. - // - /* - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture on the gpu too! - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.worldAlpha = this.worldAlpha; - - Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); - - return; - } - - */ - - if (this.glDirty) - { - this.dirty = true; - this.glDirty = false; - } - - renderer.setObjectRenderer(renderer.objectRenderers.graphics); - renderer.objectRenderers.graphics.render(this); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} - * @private - */ -Graphics.prototype.renderCanvas = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.alpha <= 0 || this.isMask === true) - { - return; - } - - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.alpha = this.alpha; - - Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); - - return; - } - else - { - var context = renderer.context; - var transform = this.worldTransform; - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - var resolution = renderer.resolution; - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } - } -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @return {Rectangle} the rectangular bounding area - */ -Graphics.prototype.getBounds = function (matrix) -{ - // return an empty object if the item is a mask! - if (this.isMask) - { - return math.Rectangle.EMPTY; - } - - if (this.dirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.dirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - return this._bounds; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; - - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; -}; - -/** - * Generates the cached sprite when the sprite has cacheAsBitmap = true - * - * @private - */ -Graphics.prototype._generateCachedSprite = function () -{ - var bounds = this.getLocalBounds(); - - if (!this._cachedSprite) - { - var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); - var texture = Texture.fromCanvas(canvasBuffer.canvas); - - this._cachedSprite = new Sprite(texture); - this._cachedSprite.buffer = canvasBuffer; - - this._cachedSprite.worldTransform = this.worldTransform; - } - else - { - this._cachedSprite.buffer.resize(bounds.width, bounds.height); - } - - // leverage the anchor to account for the offset of the element - this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); - this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); - - // this._cachedSprite.buffer.context.save(); - this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); - - // make sure we set the alpha of the graphics to 1 for the render.. - this.worldAlpha = 1; - - // now render the graphic.. - CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); - - this._cachedSprite.alpha = this.alpha; -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Graphics.prototype.updateCachedSpriteTexture = function () -{ - var cachedSprite = this._cachedSprite; - var texture = cachedSprite.texture; - var canvas = cachedSprite.buffer.canvas; - - texture.baseTexture.width = canvas.width; - texture.baseTexture.height = canvas.height; - texture.crop.width = texture.frame.width = canvas.width; - texture.crop.height = texture.frame.height = canvas.height; - - cachedSprite._width = canvas.width; - cachedSprite._height = canvas.height; - - // update the dirty base textures - texture.baseTexture.dirty(); -}; - -/** - * Destroys a previous cached sprite. - * - */ -Graphics.prototype.destroyCachedSprite = function () -{ - this._cachedSprite.texture.destroy(true); - - // let the gc collect the unused sprite - // TODO could be object pooled! - this._cachedSprite = null; -}; - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. - * @return {GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = this.filling; - this.currentPath = data; - } - - this.dirty = true; - - return data; -}; diff --git a/src/core/primitives/GraphicsData.js b/src/core/primitives/GraphicsData.js deleted file mode 100644 index 788f6f2..0000000 --- a/src/core/primitives/GraphicsData.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * A GraphicsData object. - * - * @class - * @namespace PIXI - */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - this.lineWidth = lineWidth; - this.lineColor = lineColor; - this.lineAlpha = lineAlpha; - this._lineTint = lineColor; - - this.fillColor = fillColor; - this.fillAlpha = fillAlpha; - this._fillTint = fillColor; - this.fill = fill; - - this.shape = shape; - this.type = shape.type; -} - -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {GraphicsData} - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js deleted file mode 100644 index 1a508f9..0000000 --- a/src/core/primitives/GraphicsRenderer.js +++ /dev/null @@ -1,874 +0,0 @@ -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; - -/** - * 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 ); - -// var matrix = graphics.worldTransform.clone(); -// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); -// matrix.append(graphics.worldTransform); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.maskManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.maskManager.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.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); - - 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/primitives/WebGLGraphicsData.js b/src/core/primitives/WebGLGraphicsData.js deleted file mode 100755 index 41be102..0000000 --- a/src/core/primitives/WebGLGraphicsData.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @class - * @private - */ -function WebGLGraphicsData(gl) { - this.gl = gl; - - //TODO does this need to be split before uploding?? - this.color = [0,0,0]; // color split! - this.points = []; - this.indices = []; - this.buffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - this.mode = 1; - this.alpha = 1; - this.dirty = true; -} - -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; -module.exports = WebGLGraphicsData; - -/** - * - */ -WebGLGraphicsData.prototype.reset = function () { - this.points = []; - this.indices = []; -}; - -/** - * - */ -WebGLGraphicsData.prototype.upload = function () { - var gl = this.gl; - -// this.lastIndex = graphics.graphicsData.length; - this.glPoints = new Float32Array(this.points); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); - - this.glIndicies = new Uint16Array(this.indices); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); - - this.dirty = false; -}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6c1fb7d..4e82baa 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -174,6 +174,9 @@ // time to create the render managers! each one focuses on managing a state in webGL + // initialize the context so it is ready for the managers. + this._initContext(); + /** * Deals with managing the shader programs and their attribs * @member {WebGLShaderManager} @@ -204,17 +207,11 @@ */ this.blendModeManager = new WebGLBlendModeManager(this); - - this.blendModes = null; this._boundUpdateTexture = this.updateTexture.bind(this); this._boundDestroyTexture = this.destroyTexture.bind(this); - - - // time init the context.. - this._initContext(); this.currentRenderTarget = this.renderTarget; @@ -228,27 +225,18 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - /** - * Manages the renderer of specific objects. - * - * @member {object} - */ - this.objectRenderers = {}; - this.currentRenderer = new ObjectRenderer(); - - // create an instance of each registered object renderer - for (var o in WebGLRenderer._objectRenderers) { - this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); - } - + this.initPlugins(); } // constructor WebGLRenderer.prototype.constructor = WebGLRenderer; module.exports = WebGLRenderer; +WebGLRenderer.glContextId = 0; + +utils.pluginTarget.mixin(WebGLRenderer); utils.eventTarget.mixin(WebGLRenderer.prototype); Object.defineProperties(WebGLRenderer.prototype, { @@ -297,11 +285,9 @@ gl.enable(gl.BLEND); this.renderTarget = new RenderTarget(this.gl, this.width, this.height, null, true); - this.emit('context', gl); - // now resize and we are good to go! this.resize(this.width, this.height); }; @@ -349,7 +335,7 @@ gl.clear(gl.COLOR_BUFFER_BIT); } - this.renderDisplayObject(object, this.renderTarget)//this.projection); + this.renderDisplayObject(object, this.renderTarget);//this.projection); }; /** @@ -536,12 +522,7 @@ this.maskManager.destroy(); this.filterManager.destroy(); - for (var o in this.objectRenderers) { - this.objectRenderers[o].destroy(); - this.objectRenderers[o] = null; - } - - this.objectRenderers = null; + this.destroyPlugins(); // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -611,11 +592,3 @@ this.blendModes[CONST.blendModes.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; } }; - -WebGLRenderer.glContextId = 0; -WebGLRenderer._objectRenderers = {}; - -WebGLRenderer.registerObjectRenderer = function (name, ctor) { - WebGLRenderer._objectRenderers[name] = ctor; -}; - diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js index 87c99f0..d6ed3c6 100644 --- a/src/core/renderers/webgl/managers/WebGLFilterManager.js +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -51,7 +51,7 @@ */ WebGLFilterManager.prototype.begin = function (buffer) { - this.defaultShader = this.renderer.shaderManager.defaultShader; + this.defaultShader = this.renderer.shaderManager.plugins.defaultShader; this.width = this.renderer.projection.x * 2; this.height = -this.renderer.projection.y * 2; @@ -385,7 +385,7 @@ if (!shader) { - shader = new Shader(gl); + shader = new Shader(this.renderer.shaderManager); shader.fragmentSrc = filter.fragmentSrc; shader.uniforms = filter.uniforms; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js index 6a1f782..d6a11c1 100644 --- a/src/core/renderers/webgl/managers/WebGLMaskManager.js +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -126,11 +126,11 @@ // bind the graphics object.. var projection = this.renderer.projection, offset = this.renderer.offset, - shader;// = this.renderer.shaderManager.primitiveShader; + shader;// = this.renderer.shaderManager.plugins.primitiveShader; if (webGLData.mode === 1) { - shader = this.renderer.shaderManager.complexPrimitiveShader; + shader = this.renderer.shaderManager.plugins.complexPrimitiveShader; this.renderer.shaderManager.setShader(shader); @@ -156,7 +156,7 @@ else { //this.renderer.shaderManager.activatePrimitiveShader(); - shader = this.renderer.shaderManager.primitiveShader; + shader = this.renderer.shaderManager.plugins.primitiveShader; this.renderer.shaderManager.setShader( shader ); gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); @@ -292,11 +292,11 @@ */ WebGLMaskManager.prototype.pushMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); if (maskData.dirty) { - this.renderer.objectRenderers.graphics.updateGraphics(maskData, this.renderer.gl); + this.renderer.plugins.graphics.updateGraphics(maskData, this.renderer.gl); } if (!maskData._webGL[this.renderer.gl.id].data.length) @@ -314,7 +314,7 @@ */ WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); this.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); }; diff --git a/src/core/renderers/webgl/managers/WebGLShaderManager.js b/src/core/renderers/webgl/managers/WebGLShaderManager.js index 90c6477..9ebb58e 100644 --- a/src/core/renderers/webgl/managers/WebGLShaderManager.js +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -1,9 +1,5 @@ var WebGLManager = require('./WebGLManager'), - PrimitiveShader = require('../shaders/PrimitiveShader'), - ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), - Shader = require('../shaders/Shader'), - FastShader = require('../shaders/FastShader'), - StripShader = require('../shaders/StripShader'); + utils = require('../../../utils'); /** * @class @@ -51,41 +47,16 @@ */ this.currentShader = null; - // this shader is used for rendering primitives - this.primitiveShader = null; - - // this shader is used for rendering triangle strips - this.complexPrimitiveShader = null; - - // this shader is used for the default sprite rendering - this.defaultShader = null; - - // this shader is used for the fast sprite rendering - this.fastShader = null; - - // the next one is used for rendering triangle strips - this.stripShader = null; + this.initPlugins(); // listen for context and update necessary shaders var self = this; - this.renderer.on('context', function (event) + this.renderer.on('context', function () { - var gl = event.data; - - // this shader is used for rendering primitives - self.primitiveShader = new PrimitiveShader(gl); - - // this shader is used for rendering triangle strips - self.complexPrimitiveShader = new ComplexPrimitiveShader(gl); - - // this shader is used for the default sprite rendering - self.defaultShader = new Shader(gl); - - // this shader is used for the fast sprite rendering - self.fastShader = new FastShader(gl); - - // the next one is used for rendering triangle strips - self.stripShader = new StripShader(gl); + for (var o in this.plugins) + { + this.plugins[o] = new (this.plugins[o].constructor)(self); + } self.setShader(self.defaultShader); }); @@ -95,6 +66,8 @@ WebGLShaderManager.prototype.constructor = WebGLShaderManager; module.exports = WebGLShaderManager; +utils.pluginTarget.mixin(WebGLShaderManager); + /** * Takes the attributes given in parameters. * @@ -164,24 +137,11 @@ */ WebGLShaderManager.prototype.destroy = function () { + this.destroyPlugins(); + this.attribState = null; this.tempAttribState = null; - this.primitiveShader.destroy(); - this.primitiveShader = null; - - this.complexPrimitiveShader.destroy(); - this.complexPrimitiveShader = null; - - this.defaultShader.destroy(); - this.defaultShader = null; - - this.fastShader.destroy(); - this.fastShader = null; - - this.stripShader.destroy(); - this.stripShader = null; - this.renderer = null; }; diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js new file mode 100644 index 0000000..41be102 --- /dev/null +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -0,0 +1,48 @@ +/** + * @class + * @private + */ +function WebGLGraphicsData(gl) { + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +} + +WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; +module.exports = WebGLGraphicsData; + +/** + * + */ +WebGLGraphicsData.prototype.reset = function () { + this.points = []; + this.indices = []; +}; + +/** + * + */ +WebGLGraphicsData.prototype.upload = function () { + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; diff --git a/src/core/index.js b/src/core/index.js index 6f744e9..1d0a398 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -23,12 +23,12 @@ Sprite: require('./sprites/Sprite'), SpriteBatch: require('./sprites/SpriteBatch'), - SpriteRenderer: require('./sprites/SpriteRenderer'), + SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), // primitives - Graphics: require('./primitives/Graphics'), - GraphicsData: require('./primitives/GraphicsData'), - GraphicsRenderer: require('./primitives/GraphicsRenderer'), + Graphics: require('./graphics/Graphics'), + GraphicsData: require('./graphics/GraphicsData'), + GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,10 +42,8 @@ CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), // renderers - webgl - WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), - // WebGLGraphics: require('./renderers/webgl/utils/WebGLGraphics'), - - + WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), + Shader: require('./renderers/webgl/shaders/Shader'), /** * This helper function will automatically detect which renderer you should be using. @@ -103,7 +101,3 @@ return core.autoDetectRenderer(width, height, options, isAndroid); } }; - -// Adding here for now.. sure there is a better place! -core.WebGLRenderer.registerObjectRenderer('sprite', core.SpriteRenderer); -core.WebGLRenderer.registerObjectRenderer('graphics', core.GraphicsRenderer); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index a086950..81de05c 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -268,6 +268,6 @@ matrix.ty = this.ty; return matrix; -} +}; Matrix.IDENTITY = new Matrix(); diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js deleted file mode 100644 index 8db9b45..0000000 --- a/src/core/primitives/Graphics.js +++ /dev/null @@ -1,1116 +0,0 @@ -var DisplayObjectContainer = require('../display/DisplayObjectContainer'), - //Sprite = require('../display/Sprite'), - Texture = require('../textures/Texture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), - // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), - GraphicsData = require('./GraphicsData'), - math = require('../math'), - CONST = require('../const'); - -/** - * The Graphics class contains methods used to draw primitive shapes such as lines, circles and - * rectangles to the display, and color and fill them. - * - * @class - * @extends DisplayObjectContainer - * @namespace PIXI - */ -function Graphics() -{ - DisplayObjectContainer.call(this); - - this.renderable = true; - - /** - * The alpha value used when filling the Graphics object. - * - * @member {number} - * @default 1 - */ - this.fillAlpha = 1; - - /** - * The width (thickness) of any lines drawn. - * - * @member {number} - * @default 0 - */ - this.lineWidth = 0; - - /** - * The color of any lines drawn. - * - * @member {string} - * @default 0 - */ - this.lineColor = 0; - - /** - * Graphics data - * - * @member {GraphicsData[]} - * @private - */ - this.graphicsData = []; - - /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * Current path - * - * @member {GraphicsData} - * @private - */ - this.currentPath = null; - - /** - * Array containing some WebGL-related properties used by the WebGL renderer. - * - * @member {object} - * @private - */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; - - /** - * Whether this shape is being used as a mask. - * - * @member {boolean} - */ - this.isMask = false; - - /** - * The bounds' padding used for bounds calculation. - * - * @member {number} - */ - this.boundsPadding = 0; - - /** - * A cache of the local bounds to prevent recalculation. - * - * @member {Rectangle} - * @private - */ - this._localBounds = new math.Rectangle(0,0,1,1); - - /** - * Used to detect if the graphics object has changed. If this is set to true then the graphics - * object will be recalculated. - * - * @member {boolean} - * @private - */ - this.dirty = true; - - /** - * Used to detect if the WebGL graphics object has changed. If this is set to true then the - * graphics object will be recalculated. - * - * @member {boolean} - * @private - */ - this.glDirty = false; - - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; -} - -// constructor -Graphics.prototype = Object.create(DisplayObjectContainer.prototype); -Graphics.prototype.constructor = Graphics; -module.exports = Graphics; - -Object.defineProperties(Graphics.prototype, { - /** - * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. - * This is useful if your graphics element does not change often, as it will speed up the rendering - * of the object in exchange for taking up texture memory. It is also useful if you need the graphics - * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if - * you are constantly redrawing the graphics element. - * - * @member {boolean} - * @memberof Graphics# - * @default false - * @private - */ - cacheAsBitmap: { - get: function () - { - return this._cacheAsBitmap; - }, - set: function (value) - { - this._cacheAsBitmap = value; - - if (this._cacheAsBitmap) - { - this._generateCachedSprite(); - } - else - { - this.destroyCachedSprite(); - this.dirty = true; - } - } - } -}); - -/** - * Creates a new Graphics object with the same values as this one. - * - * @return {Graphics} - */ -GraphicsData.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = this.dirty; - clone.glDirty = this.glDirty; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData.clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {Graphics} - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (arguments.length < 3) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); - } - else - { - // otherwise its empty so lets just set the line properties - this.currentPath.lineWidth = this.lineWidth; - this.currentPath.lineColor = this.lineColor; - this.currentPath.lineAlpha = this.lineAlpha; - } - } - - return this; -}; - -/** - * Moves the current drawing position to x, y. - * - * @param x {number} the X coordinate to move to - * @param y {number} the Y coordinate to move to - * @return {Graphics} - */ -Graphics.prototype.moveTo = function (x, y) -{ - this.drawShape(new math.Polygon([x,y])); - - return this; -}; - -/** - * Draws a line using the current line style from the current drawing position to (x, y); - * The current drawing position is then set to (x, y). - * - * @param x {number} the X coordinate to draw to - * @param y {number} the Y coordinate to draw to - * @return {Graphics} - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a quadratic bezier curve and then draws it. - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @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 {Graphics} - */ -Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var xa, - ya, - n = 20, - points = this.currentPath.shape.points; - - if (points.length === 0) - { - this.moveTo(0, 0); - } - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - for (var i = 1; i <= n; ++i) - { - j = i / n; - - xa = fromX + ( (cpX - fromX) * j ); - ya = fromY + ( (cpY - fromY) * j ); - - points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), - ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); - } - - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a bezier curve and then draws it. - * - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param cpX2 {number} Second Control point x - * @param cpY2 {number} Second Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {Graphics} - */ -Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var n = 20, - dt, - dt2, - dt3, - t2, - t3, - points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - - for (var i = 1; i <= n; ++i) - { - j = i / n; - - dt = (1 - j); - dt2 = dt * dt; - dt3 = dt2 * dt; - - t2 = j * j; - t3 = t2 * j; - - points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, - dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); - } - - this.dirty = true; - - return this; -}; - -/** - * The arcTo() method creates an arc/curve between two tangents on the canvas. - * - * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! - * - * @param x1 {number} The x-coordinate of the beginning of the arc - * @param y1 {number} The y-coordinate of the beginning of the arc - * @param x2 {number} The x-coordinate of the end of the arc - * @param y2 {number} The y-coordinate of the end of the arc - * @param radius {number} The radius of the arc - * @return {Graphics} - */ -Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points.push(x1, y1); - } - } - else - { - this.moveTo(x1, y1); - } - - var points = this.currentPath.shape.points, - fromX = points[points.length-2], - fromY = points[points.length-1], - a1 = fromY - y1, - b1 = fromX - x1, - a2 = y2 - y1, - b2 = x2 - x1, - mm = Math.abs(a1 * b2 - b1 * a2); - - if (mm < 1.0e-8 || radius === 0) - { - if (points[points.length-2] !== x1 || points[points.length-1] !== y1) - { - points.push(x1, y1); - } - } - else - { - var dd = a1 * a1 + b1 * b1, - cc = a2 * a2 + b2 * b2, - tt = a1 * a2 + b1 * b2, - k1 = radius * Math.sqrt(dd) / mm, - k2 = radius * Math.sqrt(cc) / mm, - j1 = k1 * tt / dd, - j2 = k2 * tt / cc, - cx = k1 * b2 + k2 * b1, - cy = k1 * a2 + k2 * a1, - px = b1 * (k2 + j1), - py = a1 * (k2 + j1), - qx = b2 * (k1 + j2), - qy = a2 * (k1 + j2), - startAngle = Math.atan2(py - cy, px - cx), - endAngle = Math.atan2(qy - cy, qx - cx); - - this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); - } - - this.dirty = true; - - return this; -}; - -/** - * The arc method creates an arc/curve (used to create circles, or parts of circles). - * - * @param cx {number} The x-coordinate of the center of the circle - * @param cy {number} The y-coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) - * @param endAngle {number} The ending angle, in radians - * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {Graphics} - */ -Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - var points; - - // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... - if (this.currentPath) - { - points = this.currentPath.shape.points; - - if (points.length === 0) - { - points.push(startX, startY); - } - else if (points[points.length-2] !== startX || points[points.length-1] !== startY) - { - points.push(startX, startY); - } - } - else - { - this.moveTo(startX, startY); - points = this.currentPath.shape.points; - } - - if (startAngle === endAngle) - { - return this; - } - - if (!anticlockwise && endAngle <= startAngle) - { - endAngle += Math.PI * 2; - } - else if (anticlockwise && startAngle <= endAngle) - { - startAngle += Math.PI * 2; - } - - var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); - var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; - - if (sweep === 0) - { - return this; - } - - var theta = sweep/(segs*2); - var theta2 = theta*2; - - var cTheta = Math.cos(theta); - var sTheta = Math.sin(theta); - - var segMinus = segs - 1; - - var remainder = ( segMinus % 1 ) / segMinus; - - for (var i = 0; i <= segMinus; ++i) - { - var real = i + remainder * i; - var angle = ((theta) + startAngle + (theta2 * real)); - - var c = Math.cos(angle); - var s = -Math.sin(angle); - - points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, - ( (cTheta * -s) + (sTheta * c) ) * radius + cy); - } - - this.dirty = true; - - return this; -}; - -/** - * Specifies a simple one-color fill that subsequent calls to other Graphics methods - * (such as lineTo() or drawCircle()) use when drawing. - * - * @param color {number} the color of the fill - * @param alpha {number} the alpha of the fill - * @return {Graphics} - */ -Graphics.prototype.beginFill = function (color, alpha) -{ - this.filling = true; - this.fillColor = color || 0; - this.fillAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length <= 2) - { - this.currentPath.fill = this.filling; - this.currentPath.fillColor = this.fillColor; - this.currentPath.fillAlpha = this.fillAlpha; - } - } - return this; -}; - -/** - * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. - * - * @return {Graphics} - */ -Graphics.prototype.endFill = function () -{ - this.filling = false; - this.fillColor = null; - this.fillAlpha = 1; - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @return {Graphics} - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {Graphics} - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {Graphics} - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {Array} The path data used to construct the polygon. - * @return {Graphics} - */ -Graphics.prototype.drawPolygon = function (path) -{ - if (!(path instanceof Array)) - { - path = Array.prototype.slice.call(arguments); - } - - this.drawShape(new math.Polygon(path)); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {Graphics} - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty = true; - this.clearDirty = true; - this.graphicsData = []; - - return this; -}; - -/** - * Useful function that returns a texture of the graphics object that can then be used to create sprites - * This can be quite useful if your geometry is complicated and needs to be reused multiple times. - * - * @param resolution {number} The resolution of the texture being generated - * @param scaleMode {number} Should be one of the scaleMode consts - * @return {Texture} a texture of the graphics object - */ -Graphics.prototype.generateTexture = function (resolution, scaleMode) -{ - resolution = resolution || 1; - - var bounds = this.getBounds(); - - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); - - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - canvasBuffer.context.scale(resolution, resolution); - - canvasBuffer.context.translate(-bounds.x,-bounds.y); - - CanvasGraphics.renderGraphics(this, canvasBuffer.context); - - return texture; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (this.isMask === true) - { - return; - } - - // this code may still be needed so leaving for now.. - // - /* - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture on the gpu too! - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.worldAlpha = this.worldAlpha; - - Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); - - return; - } - - */ - - if (this.glDirty) - { - this.dirty = true; - this.glDirty = false; - } - - renderer.setObjectRenderer(renderer.objectRenderers.graphics); - renderer.objectRenderers.graphics.render(this); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} - * @private - */ -Graphics.prototype.renderCanvas = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.alpha <= 0 || this.isMask === true) - { - return; - } - - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.alpha = this.alpha; - - Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); - - return; - } - else - { - var context = renderer.context; - var transform = this.worldTransform; - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - var resolution = renderer.resolution; - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } - } -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @return {Rectangle} the rectangular bounding area - */ -Graphics.prototype.getBounds = function (matrix) -{ - // return an empty object if the item is a mask! - if (this.isMask) - { - return math.Rectangle.EMPTY; - } - - if (this.dirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.dirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - return this._bounds; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; - - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; -}; - -/** - * Generates the cached sprite when the sprite has cacheAsBitmap = true - * - * @private - */ -Graphics.prototype._generateCachedSprite = function () -{ - var bounds = this.getLocalBounds(); - - if (!this._cachedSprite) - { - var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); - var texture = Texture.fromCanvas(canvasBuffer.canvas); - - this._cachedSprite = new Sprite(texture); - this._cachedSprite.buffer = canvasBuffer; - - this._cachedSprite.worldTransform = this.worldTransform; - } - else - { - this._cachedSprite.buffer.resize(bounds.width, bounds.height); - } - - // leverage the anchor to account for the offset of the element - this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); - this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); - - // this._cachedSprite.buffer.context.save(); - this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); - - // make sure we set the alpha of the graphics to 1 for the render.. - this.worldAlpha = 1; - - // now render the graphic.. - CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); - - this._cachedSprite.alpha = this.alpha; -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Graphics.prototype.updateCachedSpriteTexture = function () -{ - var cachedSprite = this._cachedSprite; - var texture = cachedSprite.texture; - var canvas = cachedSprite.buffer.canvas; - - texture.baseTexture.width = canvas.width; - texture.baseTexture.height = canvas.height; - texture.crop.width = texture.frame.width = canvas.width; - texture.crop.height = texture.frame.height = canvas.height; - - cachedSprite._width = canvas.width; - cachedSprite._height = canvas.height; - - // update the dirty base textures - texture.baseTexture.dirty(); -}; - -/** - * Destroys a previous cached sprite. - * - */ -Graphics.prototype.destroyCachedSprite = function () -{ - this._cachedSprite.texture.destroy(true); - - // let the gc collect the unused sprite - // TODO could be object pooled! - this._cachedSprite = null; -}; - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. - * @return {GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = this.filling; - this.currentPath = data; - } - - this.dirty = true; - - return data; -}; diff --git a/src/core/primitives/GraphicsData.js b/src/core/primitives/GraphicsData.js deleted file mode 100644 index 788f6f2..0000000 --- a/src/core/primitives/GraphicsData.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * A GraphicsData object. - * - * @class - * @namespace PIXI - */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - this.lineWidth = lineWidth; - this.lineColor = lineColor; - this.lineAlpha = lineAlpha; - this._lineTint = lineColor; - - this.fillColor = fillColor; - this.fillAlpha = fillAlpha; - this._fillTint = fillColor; - this.fill = fill; - - this.shape = shape; - this.type = shape.type; -} - -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {GraphicsData} - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js deleted file mode 100644 index 1a508f9..0000000 --- a/src/core/primitives/GraphicsRenderer.js +++ /dev/null @@ -1,874 +0,0 @@ -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; - -/** - * 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 ); - -// var matrix = graphics.worldTransform.clone(); -// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); -// matrix.append(graphics.worldTransform); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.maskManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.maskManager.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.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); - - 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/primitives/WebGLGraphicsData.js b/src/core/primitives/WebGLGraphicsData.js deleted file mode 100755 index 41be102..0000000 --- a/src/core/primitives/WebGLGraphicsData.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @class - * @private - */ -function WebGLGraphicsData(gl) { - this.gl = gl; - - //TODO does this need to be split before uploding?? - this.color = [0,0,0]; // color split! - this.points = []; - this.indices = []; - this.buffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - this.mode = 1; - this.alpha = 1; - this.dirty = true; -} - -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; -module.exports = WebGLGraphicsData; - -/** - * - */ -WebGLGraphicsData.prototype.reset = function () { - this.points = []; - this.indices = []; -}; - -/** - * - */ -WebGLGraphicsData.prototype.upload = function () { - var gl = this.gl; - -// this.lastIndex = graphics.graphicsData.length; - this.glPoints = new Float32Array(this.points); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); - - this.glIndicies = new Uint16Array(this.indices); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); - - this.dirty = false; -}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6c1fb7d..4e82baa 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -174,6 +174,9 @@ // time to create the render managers! each one focuses on managing a state in webGL + // initialize the context so it is ready for the managers. + this._initContext(); + /** * Deals with managing the shader programs and their attribs * @member {WebGLShaderManager} @@ -204,17 +207,11 @@ */ this.blendModeManager = new WebGLBlendModeManager(this); - - this.blendModes = null; this._boundUpdateTexture = this.updateTexture.bind(this); this._boundDestroyTexture = this.destroyTexture.bind(this); - - - // time init the context.. - this._initContext(); this.currentRenderTarget = this.renderTarget; @@ -228,27 +225,18 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - /** - * Manages the renderer of specific objects. - * - * @member {object} - */ - this.objectRenderers = {}; - this.currentRenderer = new ObjectRenderer(); - - // create an instance of each registered object renderer - for (var o in WebGLRenderer._objectRenderers) { - this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); - } - + this.initPlugins(); } // constructor WebGLRenderer.prototype.constructor = WebGLRenderer; module.exports = WebGLRenderer; +WebGLRenderer.glContextId = 0; + +utils.pluginTarget.mixin(WebGLRenderer); utils.eventTarget.mixin(WebGLRenderer.prototype); Object.defineProperties(WebGLRenderer.prototype, { @@ -297,11 +285,9 @@ gl.enable(gl.BLEND); this.renderTarget = new RenderTarget(this.gl, this.width, this.height, null, true); - this.emit('context', gl); - // now resize and we are good to go! this.resize(this.width, this.height); }; @@ -349,7 +335,7 @@ gl.clear(gl.COLOR_BUFFER_BIT); } - this.renderDisplayObject(object, this.renderTarget)//this.projection); + this.renderDisplayObject(object, this.renderTarget);//this.projection); }; /** @@ -536,12 +522,7 @@ this.maskManager.destroy(); this.filterManager.destroy(); - for (var o in this.objectRenderers) { - this.objectRenderers[o].destroy(); - this.objectRenderers[o] = null; - } - - this.objectRenderers = null; + this.destroyPlugins(); // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -611,11 +592,3 @@ this.blendModes[CONST.blendModes.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; } }; - -WebGLRenderer.glContextId = 0; -WebGLRenderer._objectRenderers = {}; - -WebGLRenderer.registerObjectRenderer = function (name, ctor) { - WebGLRenderer._objectRenderers[name] = ctor; -}; - diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js index 87c99f0..d6ed3c6 100644 --- a/src/core/renderers/webgl/managers/WebGLFilterManager.js +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -51,7 +51,7 @@ */ WebGLFilterManager.prototype.begin = function (buffer) { - this.defaultShader = this.renderer.shaderManager.defaultShader; + this.defaultShader = this.renderer.shaderManager.plugins.defaultShader; this.width = this.renderer.projection.x * 2; this.height = -this.renderer.projection.y * 2; @@ -385,7 +385,7 @@ if (!shader) { - shader = new Shader(gl); + shader = new Shader(this.renderer.shaderManager); shader.fragmentSrc = filter.fragmentSrc; shader.uniforms = filter.uniforms; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js index 6a1f782..d6a11c1 100644 --- a/src/core/renderers/webgl/managers/WebGLMaskManager.js +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -126,11 +126,11 @@ // bind the graphics object.. var projection = this.renderer.projection, offset = this.renderer.offset, - shader;// = this.renderer.shaderManager.primitiveShader; + shader;// = this.renderer.shaderManager.plugins.primitiveShader; if (webGLData.mode === 1) { - shader = this.renderer.shaderManager.complexPrimitiveShader; + shader = this.renderer.shaderManager.plugins.complexPrimitiveShader; this.renderer.shaderManager.setShader(shader); @@ -156,7 +156,7 @@ else { //this.renderer.shaderManager.activatePrimitiveShader(); - shader = this.renderer.shaderManager.primitiveShader; + shader = this.renderer.shaderManager.plugins.primitiveShader; this.renderer.shaderManager.setShader( shader ); gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); @@ -292,11 +292,11 @@ */ WebGLMaskManager.prototype.pushMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); if (maskData.dirty) { - this.renderer.objectRenderers.graphics.updateGraphics(maskData, this.renderer.gl); + this.renderer.plugins.graphics.updateGraphics(maskData, this.renderer.gl); } if (!maskData._webGL[this.renderer.gl.id].data.length) @@ -314,7 +314,7 @@ */ WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); this.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); }; diff --git a/src/core/renderers/webgl/managers/WebGLShaderManager.js b/src/core/renderers/webgl/managers/WebGLShaderManager.js index 90c6477..9ebb58e 100644 --- a/src/core/renderers/webgl/managers/WebGLShaderManager.js +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -1,9 +1,5 @@ var WebGLManager = require('./WebGLManager'), - PrimitiveShader = require('../shaders/PrimitiveShader'), - ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), - Shader = require('../shaders/Shader'), - FastShader = require('../shaders/FastShader'), - StripShader = require('../shaders/StripShader'); + utils = require('../../../utils'); /** * @class @@ -51,41 +47,16 @@ */ this.currentShader = null; - // this shader is used for rendering primitives - this.primitiveShader = null; - - // this shader is used for rendering triangle strips - this.complexPrimitiveShader = null; - - // this shader is used for the default sprite rendering - this.defaultShader = null; - - // this shader is used for the fast sprite rendering - this.fastShader = null; - - // the next one is used for rendering triangle strips - this.stripShader = null; + this.initPlugins(); // listen for context and update necessary shaders var self = this; - this.renderer.on('context', function (event) + this.renderer.on('context', function () { - var gl = event.data; - - // this shader is used for rendering primitives - self.primitiveShader = new PrimitiveShader(gl); - - // this shader is used for rendering triangle strips - self.complexPrimitiveShader = new ComplexPrimitiveShader(gl); - - // this shader is used for the default sprite rendering - self.defaultShader = new Shader(gl); - - // this shader is used for the fast sprite rendering - self.fastShader = new FastShader(gl); - - // the next one is used for rendering triangle strips - self.stripShader = new StripShader(gl); + for (var o in this.plugins) + { + this.plugins[o] = new (this.plugins[o].constructor)(self); + } self.setShader(self.defaultShader); }); @@ -95,6 +66,8 @@ WebGLShaderManager.prototype.constructor = WebGLShaderManager; module.exports = WebGLShaderManager; +utils.pluginTarget.mixin(WebGLShaderManager); + /** * Takes the attributes given in parameters. * @@ -164,24 +137,11 @@ */ WebGLShaderManager.prototype.destroy = function () { + this.destroyPlugins(); + this.attribState = null; this.tempAttribState = null; - this.primitiveShader.destroy(); - this.primitiveShader = null; - - this.complexPrimitiveShader.destroy(); - this.complexPrimitiveShader = null; - - this.defaultShader.destroy(); - this.defaultShader = null; - - this.fastShader.destroy(); - this.fastShader = null; - - this.stripShader.destroy(); - this.stripShader = null; - this.renderer = null; }; diff --git a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js b/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js deleted file mode 100644 index f8a07da..0000000 --- a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js +++ /dev/null @@ -1,53 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function ComplexPrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform vec3 tint;', - 'uniform float alpha;', - 'uniform vec3 color;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - color: { type: '3f', value: [0,0,0] }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); -ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; -module.exports = ComplexPrimitiveShader; diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js new file mode 100644 index 0000000..41be102 --- /dev/null +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -0,0 +1,48 @@ +/** + * @class + * @private + */ +function WebGLGraphicsData(gl) { + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +} + +WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; +module.exports = WebGLGraphicsData; + +/** + * + */ +WebGLGraphicsData.prototype.reset = function () { + this.points = []; + this.indices = []; +}; + +/** + * + */ +WebGLGraphicsData.prototype.upload = function () { + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; diff --git a/src/core/index.js b/src/core/index.js index 6f744e9..1d0a398 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -23,12 +23,12 @@ Sprite: require('./sprites/Sprite'), SpriteBatch: require('./sprites/SpriteBatch'), - SpriteRenderer: require('./sprites/SpriteRenderer'), + SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), // primitives - Graphics: require('./primitives/Graphics'), - GraphicsData: require('./primitives/GraphicsData'), - GraphicsRenderer: require('./primitives/GraphicsRenderer'), + Graphics: require('./graphics/Graphics'), + GraphicsData: require('./graphics/GraphicsData'), + GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,10 +42,8 @@ CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), // renderers - webgl - WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), - // WebGLGraphics: require('./renderers/webgl/utils/WebGLGraphics'), - - + WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), + Shader: require('./renderers/webgl/shaders/Shader'), /** * This helper function will automatically detect which renderer you should be using. @@ -103,7 +101,3 @@ return core.autoDetectRenderer(width, height, options, isAndroid); } }; - -// Adding here for now.. sure there is a better place! -core.WebGLRenderer.registerObjectRenderer('sprite', core.SpriteRenderer); -core.WebGLRenderer.registerObjectRenderer('graphics', core.GraphicsRenderer); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index a086950..81de05c 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -268,6 +268,6 @@ matrix.ty = this.ty; return matrix; -} +}; Matrix.IDENTITY = new Matrix(); diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js deleted file mode 100644 index 8db9b45..0000000 --- a/src/core/primitives/Graphics.js +++ /dev/null @@ -1,1116 +0,0 @@ -var DisplayObjectContainer = require('../display/DisplayObjectContainer'), - //Sprite = require('../display/Sprite'), - Texture = require('../textures/Texture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), - // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), - GraphicsData = require('./GraphicsData'), - math = require('../math'), - CONST = require('../const'); - -/** - * The Graphics class contains methods used to draw primitive shapes such as lines, circles and - * rectangles to the display, and color and fill them. - * - * @class - * @extends DisplayObjectContainer - * @namespace PIXI - */ -function Graphics() -{ - DisplayObjectContainer.call(this); - - this.renderable = true; - - /** - * The alpha value used when filling the Graphics object. - * - * @member {number} - * @default 1 - */ - this.fillAlpha = 1; - - /** - * The width (thickness) of any lines drawn. - * - * @member {number} - * @default 0 - */ - this.lineWidth = 0; - - /** - * The color of any lines drawn. - * - * @member {string} - * @default 0 - */ - this.lineColor = 0; - - /** - * Graphics data - * - * @member {GraphicsData[]} - * @private - */ - this.graphicsData = []; - - /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * Current path - * - * @member {GraphicsData} - * @private - */ - this.currentPath = null; - - /** - * Array containing some WebGL-related properties used by the WebGL renderer. - * - * @member {object} - * @private - */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; - - /** - * Whether this shape is being used as a mask. - * - * @member {boolean} - */ - this.isMask = false; - - /** - * The bounds' padding used for bounds calculation. - * - * @member {number} - */ - this.boundsPadding = 0; - - /** - * A cache of the local bounds to prevent recalculation. - * - * @member {Rectangle} - * @private - */ - this._localBounds = new math.Rectangle(0,0,1,1); - - /** - * Used to detect if the graphics object has changed. If this is set to true then the graphics - * object will be recalculated. - * - * @member {boolean} - * @private - */ - this.dirty = true; - - /** - * Used to detect if the WebGL graphics object has changed. If this is set to true then the - * graphics object will be recalculated. - * - * @member {boolean} - * @private - */ - this.glDirty = false; - - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; -} - -// constructor -Graphics.prototype = Object.create(DisplayObjectContainer.prototype); -Graphics.prototype.constructor = Graphics; -module.exports = Graphics; - -Object.defineProperties(Graphics.prototype, { - /** - * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. - * This is useful if your graphics element does not change often, as it will speed up the rendering - * of the object in exchange for taking up texture memory. It is also useful if you need the graphics - * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if - * you are constantly redrawing the graphics element. - * - * @member {boolean} - * @memberof Graphics# - * @default false - * @private - */ - cacheAsBitmap: { - get: function () - { - return this._cacheAsBitmap; - }, - set: function (value) - { - this._cacheAsBitmap = value; - - if (this._cacheAsBitmap) - { - this._generateCachedSprite(); - } - else - { - this.destroyCachedSprite(); - this.dirty = true; - } - } - } -}); - -/** - * Creates a new Graphics object with the same values as this one. - * - * @return {Graphics} - */ -GraphicsData.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = this.dirty; - clone.glDirty = this.glDirty; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData.clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {Graphics} - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (arguments.length < 3) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); - } - else - { - // otherwise its empty so lets just set the line properties - this.currentPath.lineWidth = this.lineWidth; - this.currentPath.lineColor = this.lineColor; - this.currentPath.lineAlpha = this.lineAlpha; - } - } - - return this; -}; - -/** - * Moves the current drawing position to x, y. - * - * @param x {number} the X coordinate to move to - * @param y {number} the Y coordinate to move to - * @return {Graphics} - */ -Graphics.prototype.moveTo = function (x, y) -{ - this.drawShape(new math.Polygon([x,y])); - - return this; -}; - -/** - * Draws a line using the current line style from the current drawing position to (x, y); - * The current drawing position is then set to (x, y). - * - * @param x {number} the X coordinate to draw to - * @param y {number} the Y coordinate to draw to - * @return {Graphics} - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a quadratic bezier curve and then draws it. - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @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 {Graphics} - */ -Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var xa, - ya, - n = 20, - points = this.currentPath.shape.points; - - if (points.length === 0) - { - this.moveTo(0, 0); - } - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - for (var i = 1; i <= n; ++i) - { - j = i / n; - - xa = fromX + ( (cpX - fromX) * j ); - ya = fromY + ( (cpY - fromY) * j ); - - points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), - ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); - } - - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a bezier curve and then draws it. - * - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param cpX2 {number} Second Control point x - * @param cpY2 {number} Second Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {Graphics} - */ -Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var n = 20, - dt, - dt2, - dt3, - t2, - t3, - points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - - for (var i = 1; i <= n; ++i) - { - j = i / n; - - dt = (1 - j); - dt2 = dt * dt; - dt3 = dt2 * dt; - - t2 = j * j; - t3 = t2 * j; - - points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, - dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); - } - - this.dirty = true; - - return this; -}; - -/** - * The arcTo() method creates an arc/curve between two tangents on the canvas. - * - * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! - * - * @param x1 {number} The x-coordinate of the beginning of the arc - * @param y1 {number} The y-coordinate of the beginning of the arc - * @param x2 {number} The x-coordinate of the end of the arc - * @param y2 {number} The y-coordinate of the end of the arc - * @param radius {number} The radius of the arc - * @return {Graphics} - */ -Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points.push(x1, y1); - } - } - else - { - this.moveTo(x1, y1); - } - - var points = this.currentPath.shape.points, - fromX = points[points.length-2], - fromY = points[points.length-1], - a1 = fromY - y1, - b1 = fromX - x1, - a2 = y2 - y1, - b2 = x2 - x1, - mm = Math.abs(a1 * b2 - b1 * a2); - - if (mm < 1.0e-8 || radius === 0) - { - if (points[points.length-2] !== x1 || points[points.length-1] !== y1) - { - points.push(x1, y1); - } - } - else - { - var dd = a1 * a1 + b1 * b1, - cc = a2 * a2 + b2 * b2, - tt = a1 * a2 + b1 * b2, - k1 = radius * Math.sqrt(dd) / mm, - k2 = radius * Math.sqrt(cc) / mm, - j1 = k1 * tt / dd, - j2 = k2 * tt / cc, - cx = k1 * b2 + k2 * b1, - cy = k1 * a2 + k2 * a1, - px = b1 * (k2 + j1), - py = a1 * (k2 + j1), - qx = b2 * (k1 + j2), - qy = a2 * (k1 + j2), - startAngle = Math.atan2(py - cy, px - cx), - endAngle = Math.atan2(qy - cy, qx - cx); - - this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); - } - - this.dirty = true; - - return this; -}; - -/** - * The arc method creates an arc/curve (used to create circles, or parts of circles). - * - * @param cx {number} The x-coordinate of the center of the circle - * @param cy {number} The y-coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) - * @param endAngle {number} The ending angle, in radians - * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {Graphics} - */ -Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - var points; - - // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... - if (this.currentPath) - { - points = this.currentPath.shape.points; - - if (points.length === 0) - { - points.push(startX, startY); - } - else if (points[points.length-2] !== startX || points[points.length-1] !== startY) - { - points.push(startX, startY); - } - } - else - { - this.moveTo(startX, startY); - points = this.currentPath.shape.points; - } - - if (startAngle === endAngle) - { - return this; - } - - if (!anticlockwise && endAngle <= startAngle) - { - endAngle += Math.PI * 2; - } - else if (anticlockwise && startAngle <= endAngle) - { - startAngle += Math.PI * 2; - } - - var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); - var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; - - if (sweep === 0) - { - return this; - } - - var theta = sweep/(segs*2); - var theta2 = theta*2; - - var cTheta = Math.cos(theta); - var sTheta = Math.sin(theta); - - var segMinus = segs - 1; - - var remainder = ( segMinus % 1 ) / segMinus; - - for (var i = 0; i <= segMinus; ++i) - { - var real = i + remainder * i; - var angle = ((theta) + startAngle + (theta2 * real)); - - var c = Math.cos(angle); - var s = -Math.sin(angle); - - points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, - ( (cTheta * -s) + (sTheta * c) ) * radius + cy); - } - - this.dirty = true; - - return this; -}; - -/** - * Specifies a simple one-color fill that subsequent calls to other Graphics methods - * (such as lineTo() or drawCircle()) use when drawing. - * - * @param color {number} the color of the fill - * @param alpha {number} the alpha of the fill - * @return {Graphics} - */ -Graphics.prototype.beginFill = function (color, alpha) -{ - this.filling = true; - this.fillColor = color || 0; - this.fillAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length <= 2) - { - this.currentPath.fill = this.filling; - this.currentPath.fillColor = this.fillColor; - this.currentPath.fillAlpha = this.fillAlpha; - } - } - return this; -}; - -/** - * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. - * - * @return {Graphics} - */ -Graphics.prototype.endFill = function () -{ - this.filling = false; - this.fillColor = null; - this.fillAlpha = 1; - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @return {Graphics} - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {Graphics} - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {Graphics} - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {Array} The path data used to construct the polygon. - * @return {Graphics} - */ -Graphics.prototype.drawPolygon = function (path) -{ - if (!(path instanceof Array)) - { - path = Array.prototype.slice.call(arguments); - } - - this.drawShape(new math.Polygon(path)); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {Graphics} - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty = true; - this.clearDirty = true; - this.graphicsData = []; - - return this; -}; - -/** - * Useful function that returns a texture of the graphics object that can then be used to create sprites - * This can be quite useful if your geometry is complicated and needs to be reused multiple times. - * - * @param resolution {number} The resolution of the texture being generated - * @param scaleMode {number} Should be one of the scaleMode consts - * @return {Texture} a texture of the graphics object - */ -Graphics.prototype.generateTexture = function (resolution, scaleMode) -{ - resolution = resolution || 1; - - var bounds = this.getBounds(); - - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); - - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - canvasBuffer.context.scale(resolution, resolution); - - canvasBuffer.context.translate(-bounds.x,-bounds.y); - - CanvasGraphics.renderGraphics(this, canvasBuffer.context); - - return texture; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (this.isMask === true) - { - return; - } - - // this code may still be needed so leaving for now.. - // - /* - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture on the gpu too! - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.worldAlpha = this.worldAlpha; - - Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); - - return; - } - - */ - - if (this.glDirty) - { - this.dirty = true; - this.glDirty = false; - } - - renderer.setObjectRenderer(renderer.objectRenderers.graphics); - renderer.objectRenderers.graphics.render(this); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} - * @private - */ -Graphics.prototype.renderCanvas = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.alpha <= 0 || this.isMask === true) - { - return; - } - - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.alpha = this.alpha; - - Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); - - return; - } - else - { - var context = renderer.context; - var transform = this.worldTransform; - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - var resolution = renderer.resolution; - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } - } -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @return {Rectangle} the rectangular bounding area - */ -Graphics.prototype.getBounds = function (matrix) -{ - // return an empty object if the item is a mask! - if (this.isMask) - { - return math.Rectangle.EMPTY; - } - - if (this.dirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.dirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - return this._bounds; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; - - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; -}; - -/** - * Generates the cached sprite when the sprite has cacheAsBitmap = true - * - * @private - */ -Graphics.prototype._generateCachedSprite = function () -{ - var bounds = this.getLocalBounds(); - - if (!this._cachedSprite) - { - var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); - var texture = Texture.fromCanvas(canvasBuffer.canvas); - - this._cachedSprite = new Sprite(texture); - this._cachedSprite.buffer = canvasBuffer; - - this._cachedSprite.worldTransform = this.worldTransform; - } - else - { - this._cachedSprite.buffer.resize(bounds.width, bounds.height); - } - - // leverage the anchor to account for the offset of the element - this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); - this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); - - // this._cachedSprite.buffer.context.save(); - this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); - - // make sure we set the alpha of the graphics to 1 for the render.. - this.worldAlpha = 1; - - // now render the graphic.. - CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); - - this._cachedSprite.alpha = this.alpha; -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Graphics.prototype.updateCachedSpriteTexture = function () -{ - var cachedSprite = this._cachedSprite; - var texture = cachedSprite.texture; - var canvas = cachedSprite.buffer.canvas; - - texture.baseTexture.width = canvas.width; - texture.baseTexture.height = canvas.height; - texture.crop.width = texture.frame.width = canvas.width; - texture.crop.height = texture.frame.height = canvas.height; - - cachedSprite._width = canvas.width; - cachedSprite._height = canvas.height; - - // update the dirty base textures - texture.baseTexture.dirty(); -}; - -/** - * Destroys a previous cached sprite. - * - */ -Graphics.prototype.destroyCachedSprite = function () -{ - this._cachedSprite.texture.destroy(true); - - // let the gc collect the unused sprite - // TODO could be object pooled! - this._cachedSprite = null; -}; - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. - * @return {GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = this.filling; - this.currentPath = data; - } - - this.dirty = true; - - return data; -}; diff --git a/src/core/primitives/GraphicsData.js b/src/core/primitives/GraphicsData.js deleted file mode 100644 index 788f6f2..0000000 --- a/src/core/primitives/GraphicsData.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * A GraphicsData object. - * - * @class - * @namespace PIXI - */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - this.lineWidth = lineWidth; - this.lineColor = lineColor; - this.lineAlpha = lineAlpha; - this._lineTint = lineColor; - - this.fillColor = fillColor; - this.fillAlpha = fillAlpha; - this._fillTint = fillColor; - this.fill = fill; - - this.shape = shape; - this.type = shape.type; -} - -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {GraphicsData} - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js deleted file mode 100644 index 1a508f9..0000000 --- a/src/core/primitives/GraphicsRenderer.js +++ /dev/null @@ -1,874 +0,0 @@ -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; - -/** - * 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 ); - -// var matrix = graphics.worldTransform.clone(); -// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); -// matrix.append(graphics.worldTransform); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.maskManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.maskManager.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.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); - - 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/primitives/WebGLGraphicsData.js b/src/core/primitives/WebGLGraphicsData.js deleted file mode 100755 index 41be102..0000000 --- a/src/core/primitives/WebGLGraphicsData.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @class - * @private - */ -function WebGLGraphicsData(gl) { - this.gl = gl; - - //TODO does this need to be split before uploding?? - this.color = [0,0,0]; // color split! - this.points = []; - this.indices = []; - this.buffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - this.mode = 1; - this.alpha = 1; - this.dirty = true; -} - -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; -module.exports = WebGLGraphicsData; - -/** - * - */ -WebGLGraphicsData.prototype.reset = function () { - this.points = []; - this.indices = []; -}; - -/** - * - */ -WebGLGraphicsData.prototype.upload = function () { - var gl = this.gl; - -// this.lastIndex = graphics.graphicsData.length; - this.glPoints = new Float32Array(this.points); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); - - this.glIndicies = new Uint16Array(this.indices); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); - - this.dirty = false; -}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6c1fb7d..4e82baa 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -174,6 +174,9 @@ // time to create the render managers! each one focuses on managing a state in webGL + // initialize the context so it is ready for the managers. + this._initContext(); + /** * Deals with managing the shader programs and their attribs * @member {WebGLShaderManager} @@ -204,17 +207,11 @@ */ this.blendModeManager = new WebGLBlendModeManager(this); - - this.blendModes = null; this._boundUpdateTexture = this.updateTexture.bind(this); this._boundDestroyTexture = this.destroyTexture.bind(this); - - - // time init the context.. - this._initContext(); this.currentRenderTarget = this.renderTarget; @@ -228,27 +225,18 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - /** - * Manages the renderer of specific objects. - * - * @member {object} - */ - this.objectRenderers = {}; - this.currentRenderer = new ObjectRenderer(); - - // create an instance of each registered object renderer - for (var o in WebGLRenderer._objectRenderers) { - this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); - } - + this.initPlugins(); } // constructor WebGLRenderer.prototype.constructor = WebGLRenderer; module.exports = WebGLRenderer; +WebGLRenderer.glContextId = 0; + +utils.pluginTarget.mixin(WebGLRenderer); utils.eventTarget.mixin(WebGLRenderer.prototype); Object.defineProperties(WebGLRenderer.prototype, { @@ -297,11 +285,9 @@ gl.enable(gl.BLEND); this.renderTarget = new RenderTarget(this.gl, this.width, this.height, null, true); - this.emit('context', gl); - // now resize and we are good to go! this.resize(this.width, this.height); }; @@ -349,7 +335,7 @@ gl.clear(gl.COLOR_BUFFER_BIT); } - this.renderDisplayObject(object, this.renderTarget)//this.projection); + this.renderDisplayObject(object, this.renderTarget);//this.projection); }; /** @@ -536,12 +522,7 @@ this.maskManager.destroy(); this.filterManager.destroy(); - for (var o in this.objectRenderers) { - this.objectRenderers[o].destroy(); - this.objectRenderers[o] = null; - } - - this.objectRenderers = null; + this.destroyPlugins(); // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -611,11 +592,3 @@ this.blendModes[CONST.blendModes.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; } }; - -WebGLRenderer.glContextId = 0; -WebGLRenderer._objectRenderers = {}; - -WebGLRenderer.registerObjectRenderer = function (name, ctor) { - WebGLRenderer._objectRenderers[name] = ctor; -}; - diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js index 87c99f0..d6ed3c6 100644 --- a/src/core/renderers/webgl/managers/WebGLFilterManager.js +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -51,7 +51,7 @@ */ WebGLFilterManager.prototype.begin = function (buffer) { - this.defaultShader = this.renderer.shaderManager.defaultShader; + this.defaultShader = this.renderer.shaderManager.plugins.defaultShader; this.width = this.renderer.projection.x * 2; this.height = -this.renderer.projection.y * 2; @@ -385,7 +385,7 @@ if (!shader) { - shader = new Shader(gl); + shader = new Shader(this.renderer.shaderManager); shader.fragmentSrc = filter.fragmentSrc; shader.uniforms = filter.uniforms; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js index 6a1f782..d6a11c1 100644 --- a/src/core/renderers/webgl/managers/WebGLMaskManager.js +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -126,11 +126,11 @@ // bind the graphics object.. var projection = this.renderer.projection, offset = this.renderer.offset, - shader;// = this.renderer.shaderManager.primitiveShader; + shader;// = this.renderer.shaderManager.plugins.primitiveShader; if (webGLData.mode === 1) { - shader = this.renderer.shaderManager.complexPrimitiveShader; + shader = this.renderer.shaderManager.plugins.complexPrimitiveShader; this.renderer.shaderManager.setShader(shader); @@ -156,7 +156,7 @@ else { //this.renderer.shaderManager.activatePrimitiveShader(); - shader = this.renderer.shaderManager.primitiveShader; + shader = this.renderer.shaderManager.plugins.primitiveShader; this.renderer.shaderManager.setShader( shader ); gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); @@ -292,11 +292,11 @@ */ WebGLMaskManager.prototype.pushMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); if (maskData.dirty) { - this.renderer.objectRenderers.graphics.updateGraphics(maskData, this.renderer.gl); + this.renderer.plugins.graphics.updateGraphics(maskData, this.renderer.gl); } if (!maskData._webGL[this.renderer.gl.id].data.length) @@ -314,7 +314,7 @@ */ WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); this.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); }; diff --git a/src/core/renderers/webgl/managers/WebGLShaderManager.js b/src/core/renderers/webgl/managers/WebGLShaderManager.js index 90c6477..9ebb58e 100644 --- a/src/core/renderers/webgl/managers/WebGLShaderManager.js +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -1,9 +1,5 @@ var WebGLManager = require('./WebGLManager'), - PrimitiveShader = require('../shaders/PrimitiveShader'), - ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), - Shader = require('../shaders/Shader'), - FastShader = require('../shaders/FastShader'), - StripShader = require('../shaders/StripShader'); + utils = require('../../../utils'); /** * @class @@ -51,41 +47,16 @@ */ this.currentShader = null; - // this shader is used for rendering primitives - this.primitiveShader = null; - - // this shader is used for rendering triangle strips - this.complexPrimitiveShader = null; - - // this shader is used for the default sprite rendering - this.defaultShader = null; - - // this shader is used for the fast sprite rendering - this.fastShader = null; - - // the next one is used for rendering triangle strips - this.stripShader = null; + this.initPlugins(); // listen for context and update necessary shaders var self = this; - this.renderer.on('context', function (event) + this.renderer.on('context', function () { - var gl = event.data; - - // this shader is used for rendering primitives - self.primitiveShader = new PrimitiveShader(gl); - - // this shader is used for rendering triangle strips - self.complexPrimitiveShader = new ComplexPrimitiveShader(gl); - - // this shader is used for the default sprite rendering - self.defaultShader = new Shader(gl); - - // this shader is used for the fast sprite rendering - self.fastShader = new FastShader(gl); - - // the next one is used for rendering triangle strips - self.stripShader = new StripShader(gl); + for (var o in this.plugins) + { + this.plugins[o] = new (this.plugins[o].constructor)(self); + } self.setShader(self.defaultShader); }); @@ -95,6 +66,8 @@ WebGLShaderManager.prototype.constructor = WebGLShaderManager; module.exports = WebGLShaderManager; +utils.pluginTarget.mixin(WebGLShaderManager); + /** * Takes the attributes given in parameters. * @@ -164,24 +137,11 @@ */ WebGLShaderManager.prototype.destroy = function () { + this.destroyPlugins(); + this.attribState = null; this.tempAttribState = null; - this.primitiveShader.destroy(); - this.primitiveShader = null; - - this.complexPrimitiveShader.destroy(); - this.complexPrimitiveShader = null; - - this.defaultShader.destroy(); - this.defaultShader = null; - - this.fastShader.destroy(); - this.fastShader = null; - - this.stripShader.destroy(); - this.stripShader = null; - this.renderer = null; }; diff --git a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js b/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js deleted file mode 100644 index f8a07da..0000000 --- a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js +++ /dev/null @@ -1,53 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function ComplexPrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform vec3 tint;', - 'uniform float alpha;', - 'uniform vec3 color;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - color: { type: '3f', value: [0,0,0] }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); -ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; -module.exports = ComplexPrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/FastShader.js b/src/core/renderers/webgl/shaders/FastShader.js index d198f10..c80d040 100644 --- a/src/core/renderers/webgl/shaders/FastShader.js +++ b/src/core/renderers/webgl/shaders/FastShader.js @@ -1,15 +1,16 @@ -var Shader = require('./Shader'); +var Shader = require('./Shader'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @extends Shader * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. */ -function FastShader(gl) +function FastShader(shaderManager) { Shader.call(this, - gl, + shaderManager, // vertex shader [ 'attribute vec2 aVertexPosition;', @@ -70,3 +71,5 @@ FastShader.prototype = Object.create(Shader.prototype); FastShader.prototype.constructor = FastShader; module.exports = FastShader; + +WebGLShaderManager.registerPlugin('fastShader', FastShader); diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js new file mode 100644 index 0000000..41be102 --- /dev/null +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -0,0 +1,48 @@ +/** + * @class + * @private + */ +function WebGLGraphicsData(gl) { + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +} + +WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; +module.exports = WebGLGraphicsData; + +/** + * + */ +WebGLGraphicsData.prototype.reset = function () { + this.points = []; + this.indices = []; +}; + +/** + * + */ +WebGLGraphicsData.prototype.upload = function () { + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; diff --git a/src/core/index.js b/src/core/index.js index 6f744e9..1d0a398 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -23,12 +23,12 @@ Sprite: require('./sprites/Sprite'), SpriteBatch: require('./sprites/SpriteBatch'), - SpriteRenderer: require('./sprites/SpriteRenderer'), + SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), // primitives - Graphics: require('./primitives/Graphics'), - GraphicsData: require('./primitives/GraphicsData'), - GraphicsRenderer: require('./primitives/GraphicsRenderer'), + Graphics: require('./graphics/Graphics'), + GraphicsData: require('./graphics/GraphicsData'), + GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,10 +42,8 @@ CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), // renderers - webgl - WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), - // WebGLGraphics: require('./renderers/webgl/utils/WebGLGraphics'), - - + WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), + Shader: require('./renderers/webgl/shaders/Shader'), /** * This helper function will automatically detect which renderer you should be using. @@ -103,7 +101,3 @@ return core.autoDetectRenderer(width, height, options, isAndroid); } }; - -// Adding here for now.. sure there is a better place! -core.WebGLRenderer.registerObjectRenderer('sprite', core.SpriteRenderer); -core.WebGLRenderer.registerObjectRenderer('graphics', core.GraphicsRenderer); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index a086950..81de05c 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -268,6 +268,6 @@ matrix.ty = this.ty; return matrix; -} +}; Matrix.IDENTITY = new Matrix(); diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js deleted file mode 100644 index 8db9b45..0000000 --- a/src/core/primitives/Graphics.js +++ /dev/null @@ -1,1116 +0,0 @@ -var DisplayObjectContainer = require('../display/DisplayObjectContainer'), - //Sprite = require('../display/Sprite'), - Texture = require('../textures/Texture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), - // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), - GraphicsData = require('./GraphicsData'), - math = require('../math'), - CONST = require('../const'); - -/** - * The Graphics class contains methods used to draw primitive shapes such as lines, circles and - * rectangles to the display, and color and fill them. - * - * @class - * @extends DisplayObjectContainer - * @namespace PIXI - */ -function Graphics() -{ - DisplayObjectContainer.call(this); - - this.renderable = true; - - /** - * The alpha value used when filling the Graphics object. - * - * @member {number} - * @default 1 - */ - this.fillAlpha = 1; - - /** - * The width (thickness) of any lines drawn. - * - * @member {number} - * @default 0 - */ - this.lineWidth = 0; - - /** - * The color of any lines drawn. - * - * @member {string} - * @default 0 - */ - this.lineColor = 0; - - /** - * Graphics data - * - * @member {GraphicsData[]} - * @private - */ - this.graphicsData = []; - - /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * Current path - * - * @member {GraphicsData} - * @private - */ - this.currentPath = null; - - /** - * Array containing some WebGL-related properties used by the WebGL renderer. - * - * @member {object} - * @private - */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; - - /** - * Whether this shape is being used as a mask. - * - * @member {boolean} - */ - this.isMask = false; - - /** - * The bounds' padding used for bounds calculation. - * - * @member {number} - */ - this.boundsPadding = 0; - - /** - * A cache of the local bounds to prevent recalculation. - * - * @member {Rectangle} - * @private - */ - this._localBounds = new math.Rectangle(0,0,1,1); - - /** - * Used to detect if the graphics object has changed. If this is set to true then the graphics - * object will be recalculated. - * - * @member {boolean} - * @private - */ - this.dirty = true; - - /** - * Used to detect if the WebGL graphics object has changed. If this is set to true then the - * graphics object will be recalculated. - * - * @member {boolean} - * @private - */ - this.glDirty = false; - - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; -} - -// constructor -Graphics.prototype = Object.create(DisplayObjectContainer.prototype); -Graphics.prototype.constructor = Graphics; -module.exports = Graphics; - -Object.defineProperties(Graphics.prototype, { - /** - * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. - * This is useful if your graphics element does not change often, as it will speed up the rendering - * of the object in exchange for taking up texture memory. It is also useful if you need the graphics - * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if - * you are constantly redrawing the graphics element. - * - * @member {boolean} - * @memberof Graphics# - * @default false - * @private - */ - cacheAsBitmap: { - get: function () - { - return this._cacheAsBitmap; - }, - set: function (value) - { - this._cacheAsBitmap = value; - - if (this._cacheAsBitmap) - { - this._generateCachedSprite(); - } - else - { - this.destroyCachedSprite(); - this.dirty = true; - } - } - } -}); - -/** - * Creates a new Graphics object with the same values as this one. - * - * @return {Graphics} - */ -GraphicsData.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = this.dirty; - clone.glDirty = this.glDirty; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData.clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {Graphics} - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (arguments.length < 3) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); - } - else - { - // otherwise its empty so lets just set the line properties - this.currentPath.lineWidth = this.lineWidth; - this.currentPath.lineColor = this.lineColor; - this.currentPath.lineAlpha = this.lineAlpha; - } - } - - return this; -}; - -/** - * Moves the current drawing position to x, y. - * - * @param x {number} the X coordinate to move to - * @param y {number} the Y coordinate to move to - * @return {Graphics} - */ -Graphics.prototype.moveTo = function (x, y) -{ - this.drawShape(new math.Polygon([x,y])); - - return this; -}; - -/** - * Draws a line using the current line style from the current drawing position to (x, y); - * The current drawing position is then set to (x, y). - * - * @param x {number} the X coordinate to draw to - * @param y {number} the Y coordinate to draw to - * @return {Graphics} - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a quadratic bezier curve and then draws it. - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @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 {Graphics} - */ -Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var xa, - ya, - n = 20, - points = this.currentPath.shape.points; - - if (points.length === 0) - { - this.moveTo(0, 0); - } - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - for (var i = 1; i <= n; ++i) - { - j = i / n; - - xa = fromX + ( (cpX - fromX) * j ); - ya = fromY + ( (cpY - fromY) * j ); - - points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), - ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); - } - - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a bezier curve and then draws it. - * - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param cpX2 {number} Second Control point x - * @param cpY2 {number} Second Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {Graphics} - */ -Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var n = 20, - dt, - dt2, - dt3, - t2, - t3, - points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - - for (var i = 1; i <= n; ++i) - { - j = i / n; - - dt = (1 - j); - dt2 = dt * dt; - dt3 = dt2 * dt; - - t2 = j * j; - t3 = t2 * j; - - points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, - dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); - } - - this.dirty = true; - - return this; -}; - -/** - * The arcTo() method creates an arc/curve between two tangents on the canvas. - * - * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! - * - * @param x1 {number} The x-coordinate of the beginning of the arc - * @param y1 {number} The y-coordinate of the beginning of the arc - * @param x2 {number} The x-coordinate of the end of the arc - * @param y2 {number} The y-coordinate of the end of the arc - * @param radius {number} The radius of the arc - * @return {Graphics} - */ -Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points.push(x1, y1); - } - } - else - { - this.moveTo(x1, y1); - } - - var points = this.currentPath.shape.points, - fromX = points[points.length-2], - fromY = points[points.length-1], - a1 = fromY - y1, - b1 = fromX - x1, - a2 = y2 - y1, - b2 = x2 - x1, - mm = Math.abs(a1 * b2 - b1 * a2); - - if (mm < 1.0e-8 || radius === 0) - { - if (points[points.length-2] !== x1 || points[points.length-1] !== y1) - { - points.push(x1, y1); - } - } - else - { - var dd = a1 * a1 + b1 * b1, - cc = a2 * a2 + b2 * b2, - tt = a1 * a2 + b1 * b2, - k1 = radius * Math.sqrt(dd) / mm, - k2 = radius * Math.sqrt(cc) / mm, - j1 = k1 * tt / dd, - j2 = k2 * tt / cc, - cx = k1 * b2 + k2 * b1, - cy = k1 * a2 + k2 * a1, - px = b1 * (k2 + j1), - py = a1 * (k2 + j1), - qx = b2 * (k1 + j2), - qy = a2 * (k1 + j2), - startAngle = Math.atan2(py - cy, px - cx), - endAngle = Math.atan2(qy - cy, qx - cx); - - this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); - } - - this.dirty = true; - - return this; -}; - -/** - * The arc method creates an arc/curve (used to create circles, or parts of circles). - * - * @param cx {number} The x-coordinate of the center of the circle - * @param cy {number} The y-coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) - * @param endAngle {number} The ending angle, in radians - * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {Graphics} - */ -Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - var points; - - // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... - if (this.currentPath) - { - points = this.currentPath.shape.points; - - if (points.length === 0) - { - points.push(startX, startY); - } - else if (points[points.length-2] !== startX || points[points.length-1] !== startY) - { - points.push(startX, startY); - } - } - else - { - this.moveTo(startX, startY); - points = this.currentPath.shape.points; - } - - if (startAngle === endAngle) - { - return this; - } - - if (!anticlockwise && endAngle <= startAngle) - { - endAngle += Math.PI * 2; - } - else if (anticlockwise && startAngle <= endAngle) - { - startAngle += Math.PI * 2; - } - - var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); - var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; - - if (sweep === 0) - { - return this; - } - - var theta = sweep/(segs*2); - var theta2 = theta*2; - - var cTheta = Math.cos(theta); - var sTheta = Math.sin(theta); - - var segMinus = segs - 1; - - var remainder = ( segMinus % 1 ) / segMinus; - - for (var i = 0; i <= segMinus; ++i) - { - var real = i + remainder * i; - var angle = ((theta) + startAngle + (theta2 * real)); - - var c = Math.cos(angle); - var s = -Math.sin(angle); - - points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, - ( (cTheta * -s) + (sTheta * c) ) * radius + cy); - } - - this.dirty = true; - - return this; -}; - -/** - * Specifies a simple one-color fill that subsequent calls to other Graphics methods - * (such as lineTo() or drawCircle()) use when drawing. - * - * @param color {number} the color of the fill - * @param alpha {number} the alpha of the fill - * @return {Graphics} - */ -Graphics.prototype.beginFill = function (color, alpha) -{ - this.filling = true; - this.fillColor = color || 0; - this.fillAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length <= 2) - { - this.currentPath.fill = this.filling; - this.currentPath.fillColor = this.fillColor; - this.currentPath.fillAlpha = this.fillAlpha; - } - } - return this; -}; - -/** - * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. - * - * @return {Graphics} - */ -Graphics.prototype.endFill = function () -{ - this.filling = false; - this.fillColor = null; - this.fillAlpha = 1; - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @return {Graphics} - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {Graphics} - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {Graphics} - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {Array} The path data used to construct the polygon. - * @return {Graphics} - */ -Graphics.prototype.drawPolygon = function (path) -{ - if (!(path instanceof Array)) - { - path = Array.prototype.slice.call(arguments); - } - - this.drawShape(new math.Polygon(path)); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {Graphics} - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty = true; - this.clearDirty = true; - this.graphicsData = []; - - return this; -}; - -/** - * Useful function that returns a texture of the graphics object that can then be used to create sprites - * This can be quite useful if your geometry is complicated and needs to be reused multiple times. - * - * @param resolution {number} The resolution of the texture being generated - * @param scaleMode {number} Should be one of the scaleMode consts - * @return {Texture} a texture of the graphics object - */ -Graphics.prototype.generateTexture = function (resolution, scaleMode) -{ - resolution = resolution || 1; - - var bounds = this.getBounds(); - - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); - - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - canvasBuffer.context.scale(resolution, resolution); - - canvasBuffer.context.translate(-bounds.x,-bounds.y); - - CanvasGraphics.renderGraphics(this, canvasBuffer.context); - - return texture; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (this.isMask === true) - { - return; - } - - // this code may still be needed so leaving for now.. - // - /* - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture on the gpu too! - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.worldAlpha = this.worldAlpha; - - Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); - - return; - } - - */ - - if (this.glDirty) - { - this.dirty = true; - this.glDirty = false; - } - - renderer.setObjectRenderer(renderer.objectRenderers.graphics); - renderer.objectRenderers.graphics.render(this); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} - * @private - */ -Graphics.prototype.renderCanvas = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.alpha <= 0 || this.isMask === true) - { - return; - } - - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.alpha = this.alpha; - - Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); - - return; - } - else - { - var context = renderer.context; - var transform = this.worldTransform; - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - var resolution = renderer.resolution; - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } - } -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @return {Rectangle} the rectangular bounding area - */ -Graphics.prototype.getBounds = function (matrix) -{ - // return an empty object if the item is a mask! - if (this.isMask) - { - return math.Rectangle.EMPTY; - } - - if (this.dirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.dirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - return this._bounds; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; - - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; -}; - -/** - * Generates the cached sprite when the sprite has cacheAsBitmap = true - * - * @private - */ -Graphics.prototype._generateCachedSprite = function () -{ - var bounds = this.getLocalBounds(); - - if (!this._cachedSprite) - { - var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); - var texture = Texture.fromCanvas(canvasBuffer.canvas); - - this._cachedSprite = new Sprite(texture); - this._cachedSprite.buffer = canvasBuffer; - - this._cachedSprite.worldTransform = this.worldTransform; - } - else - { - this._cachedSprite.buffer.resize(bounds.width, bounds.height); - } - - // leverage the anchor to account for the offset of the element - this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); - this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); - - // this._cachedSprite.buffer.context.save(); - this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); - - // make sure we set the alpha of the graphics to 1 for the render.. - this.worldAlpha = 1; - - // now render the graphic.. - CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); - - this._cachedSprite.alpha = this.alpha; -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Graphics.prototype.updateCachedSpriteTexture = function () -{ - var cachedSprite = this._cachedSprite; - var texture = cachedSprite.texture; - var canvas = cachedSprite.buffer.canvas; - - texture.baseTexture.width = canvas.width; - texture.baseTexture.height = canvas.height; - texture.crop.width = texture.frame.width = canvas.width; - texture.crop.height = texture.frame.height = canvas.height; - - cachedSprite._width = canvas.width; - cachedSprite._height = canvas.height; - - // update the dirty base textures - texture.baseTexture.dirty(); -}; - -/** - * Destroys a previous cached sprite. - * - */ -Graphics.prototype.destroyCachedSprite = function () -{ - this._cachedSprite.texture.destroy(true); - - // let the gc collect the unused sprite - // TODO could be object pooled! - this._cachedSprite = null; -}; - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. - * @return {GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = this.filling; - this.currentPath = data; - } - - this.dirty = true; - - return data; -}; diff --git a/src/core/primitives/GraphicsData.js b/src/core/primitives/GraphicsData.js deleted file mode 100644 index 788f6f2..0000000 --- a/src/core/primitives/GraphicsData.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * A GraphicsData object. - * - * @class - * @namespace PIXI - */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - this.lineWidth = lineWidth; - this.lineColor = lineColor; - this.lineAlpha = lineAlpha; - this._lineTint = lineColor; - - this.fillColor = fillColor; - this.fillAlpha = fillAlpha; - this._fillTint = fillColor; - this.fill = fill; - - this.shape = shape; - this.type = shape.type; -} - -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {GraphicsData} - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js deleted file mode 100644 index 1a508f9..0000000 --- a/src/core/primitives/GraphicsRenderer.js +++ /dev/null @@ -1,874 +0,0 @@ -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; - -/** - * 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 ); - -// var matrix = graphics.worldTransform.clone(); -// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); -// matrix.append(graphics.worldTransform); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.maskManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.maskManager.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.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); - - 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/primitives/WebGLGraphicsData.js b/src/core/primitives/WebGLGraphicsData.js deleted file mode 100755 index 41be102..0000000 --- a/src/core/primitives/WebGLGraphicsData.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @class - * @private - */ -function WebGLGraphicsData(gl) { - this.gl = gl; - - //TODO does this need to be split before uploding?? - this.color = [0,0,0]; // color split! - this.points = []; - this.indices = []; - this.buffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - this.mode = 1; - this.alpha = 1; - this.dirty = true; -} - -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; -module.exports = WebGLGraphicsData; - -/** - * - */ -WebGLGraphicsData.prototype.reset = function () { - this.points = []; - this.indices = []; -}; - -/** - * - */ -WebGLGraphicsData.prototype.upload = function () { - var gl = this.gl; - -// this.lastIndex = graphics.graphicsData.length; - this.glPoints = new Float32Array(this.points); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); - - this.glIndicies = new Uint16Array(this.indices); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); - - this.dirty = false; -}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6c1fb7d..4e82baa 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -174,6 +174,9 @@ // time to create the render managers! each one focuses on managing a state in webGL + // initialize the context so it is ready for the managers. + this._initContext(); + /** * Deals with managing the shader programs and their attribs * @member {WebGLShaderManager} @@ -204,17 +207,11 @@ */ this.blendModeManager = new WebGLBlendModeManager(this); - - this.blendModes = null; this._boundUpdateTexture = this.updateTexture.bind(this); this._boundDestroyTexture = this.destroyTexture.bind(this); - - - // time init the context.. - this._initContext(); this.currentRenderTarget = this.renderTarget; @@ -228,27 +225,18 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - /** - * Manages the renderer of specific objects. - * - * @member {object} - */ - this.objectRenderers = {}; - this.currentRenderer = new ObjectRenderer(); - - // create an instance of each registered object renderer - for (var o in WebGLRenderer._objectRenderers) { - this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); - } - + this.initPlugins(); } // constructor WebGLRenderer.prototype.constructor = WebGLRenderer; module.exports = WebGLRenderer; +WebGLRenderer.glContextId = 0; + +utils.pluginTarget.mixin(WebGLRenderer); utils.eventTarget.mixin(WebGLRenderer.prototype); Object.defineProperties(WebGLRenderer.prototype, { @@ -297,11 +285,9 @@ gl.enable(gl.BLEND); this.renderTarget = new RenderTarget(this.gl, this.width, this.height, null, true); - this.emit('context', gl); - // now resize and we are good to go! this.resize(this.width, this.height); }; @@ -349,7 +335,7 @@ gl.clear(gl.COLOR_BUFFER_BIT); } - this.renderDisplayObject(object, this.renderTarget)//this.projection); + this.renderDisplayObject(object, this.renderTarget);//this.projection); }; /** @@ -536,12 +522,7 @@ this.maskManager.destroy(); this.filterManager.destroy(); - for (var o in this.objectRenderers) { - this.objectRenderers[o].destroy(); - this.objectRenderers[o] = null; - } - - this.objectRenderers = null; + this.destroyPlugins(); // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -611,11 +592,3 @@ this.blendModes[CONST.blendModes.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; } }; - -WebGLRenderer.glContextId = 0; -WebGLRenderer._objectRenderers = {}; - -WebGLRenderer.registerObjectRenderer = function (name, ctor) { - WebGLRenderer._objectRenderers[name] = ctor; -}; - diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js index 87c99f0..d6ed3c6 100644 --- a/src/core/renderers/webgl/managers/WebGLFilterManager.js +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -51,7 +51,7 @@ */ WebGLFilterManager.prototype.begin = function (buffer) { - this.defaultShader = this.renderer.shaderManager.defaultShader; + this.defaultShader = this.renderer.shaderManager.plugins.defaultShader; this.width = this.renderer.projection.x * 2; this.height = -this.renderer.projection.y * 2; @@ -385,7 +385,7 @@ if (!shader) { - shader = new Shader(gl); + shader = new Shader(this.renderer.shaderManager); shader.fragmentSrc = filter.fragmentSrc; shader.uniforms = filter.uniforms; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js index 6a1f782..d6a11c1 100644 --- a/src/core/renderers/webgl/managers/WebGLMaskManager.js +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -126,11 +126,11 @@ // bind the graphics object.. var projection = this.renderer.projection, offset = this.renderer.offset, - shader;// = this.renderer.shaderManager.primitiveShader; + shader;// = this.renderer.shaderManager.plugins.primitiveShader; if (webGLData.mode === 1) { - shader = this.renderer.shaderManager.complexPrimitiveShader; + shader = this.renderer.shaderManager.plugins.complexPrimitiveShader; this.renderer.shaderManager.setShader(shader); @@ -156,7 +156,7 @@ else { //this.renderer.shaderManager.activatePrimitiveShader(); - shader = this.renderer.shaderManager.primitiveShader; + shader = this.renderer.shaderManager.plugins.primitiveShader; this.renderer.shaderManager.setShader( shader ); gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); @@ -292,11 +292,11 @@ */ WebGLMaskManager.prototype.pushMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); if (maskData.dirty) { - this.renderer.objectRenderers.graphics.updateGraphics(maskData, this.renderer.gl); + this.renderer.plugins.graphics.updateGraphics(maskData, this.renderer.gl); } if (!maskData._webGL[this.renderer.gl.id].data.length) @@ -314,7 +314,7 @@ */ WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); this.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); }; diff --git a/src/core/renderers/webgl/managers/WebGLShaderManager.js b/src/core/renderers/webgl/managers/WebGLShaderManager.js index 90c6477..9ebb58e 100644 --- a/src/core/renderers/webgl/managers/WebGLShaderManager.js +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -1,9 +1,5 @@ var WebGLManager = require('./WebGLManager'), - PrimitiveShader = require('../shaders/PrimitiveShader'), - ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), - Shader = require('../shaders/Shader'), - FastShader = require('../shaders/FastShader'), - StripShader = require('../shaders/StripShader'); + utils = require('../../../utils'); /** * @class @@ -51,41 +47,16 @@ */ this.currentShader = null; - // this shader is used for rendering primitives - this.primitiveShader = null; - - // this shader is used for rendering triangle strips - this.complexPrimitiveShader = null; - - // this shader is used for the default sprite rendering - this.defaultShader = null; - - // this shader is used for the fast sprite rendering - this.fastShader = null; - - // the next one is used for rendering triangle strips - this.stripShader = null; + this.initPlugins(); // listen for context and update necessary shaders var self = this; - this.renderer.on('context', function (event) + this.renderer.on('context', function () { - var gl = event.data; - - // this shader is used for rendering primitives - self.primitiveShader = new PrimitiveShader(gl); - - // this shader is used for rendering triangle strips - self.complexPrimitiveShader = new ComplexPrimitiveShader(gl); - - // this shader is used for the default sprite rendering - self.defaultShader = new Shader(gl); - - // this shader is used for the fast sprite rendering - self.fastShader = new FastShader(gl); - - // the next one is used for rendering triangle strips - self.stripShader = new StripShader(gl); + for (var o in this.plugins) + { + this.plugins[o] = new (this.plugins[o].constructor)(self); + } self.setShader(self.defaultShader); }); @@ -95,6 +66,8 @@ WebGLShaderManager.prototype.constructor = WebGLShaderManager; module.exports = WebGLShaderManager; +utils.pluginTarget.mixin(WebGLShaderManager); + /** * Takes the attributes given in parameters. * @@ -164,24 +137,11 @@ */ WebGLShaderManager.prototype.destroy = function () { + this.destroyPlugins(); + this.attribState = null; this.tempAttribState = null; - this.primitiveShader.destroy(); - this.primitiveShader = null; - - this.complexPrimitiveShader.destroy(); - this.complexPrimitiveShader = null; - - this.defaultShader.destroy(); - this.defaultShader = null; - - this.fastShader.destroy(); - this.fastShader = null; - - this.stripShader.destroy(); - this.stripShader = null; - this.renderer = null; }; diff --git a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js b/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js deleted file mode 100644 index f8a07da..0000000 --- a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js +++ /dev/null @@ -1,53 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function ComplexPrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform vec3 tint;', - 'uniform float alpha;', - 'uniform vec3 color;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - color: { type: '3f', value: [0,0,0] }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); -ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; -module.exports = ComplexPrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/FastShader.js b/src/core/renderers/webgl/shaders/FastShader.js index d198f10..c80d040 100644 --- a/src/core/renderers/webgl/shaders/FastShader.js +++ b/src/core/renderers/webgl/shaders/FastShader.js @@ -1,15 +1,16 @@ -var Shader = require('./Shader'); +var Shader = require('./Shader'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @extends Shader * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. */ -function FastShader(gl) +function FastShader(shaderManager) { Shader.call(this, - gl, + shaderManager, // vertex shader [ 'attribute vec2 aVertexPosition;', @@ -70,3 +71,5 @@ FastShader.prototype = Object.create(Shader.prototype); FastShader.prototype.constructor = FastShader; module.exports = FastShader; + +WebGLShaderManager.registerPlugin('fastShader', FastShader); diff --git a/src/core/renderers/webgl/shaders/PrimitiveShader.js b/src/core/renderers/webgl/shaders/PrimitiveShader.js deleted file mode 100644 index 753c475..0000000 --- a/src/core/renderers/webgl/shaders/PrimitiveShader.js +++ /dev/null @@ -1,55 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform float alpha;', - 'uniform float flipY;', - 'uniform vec3 tint;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aColor:0 - } - ); -} - -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; -module.exports = PrimitiveShader; diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js new file mode 100644 index 0000000..41be102 --- /dev/null +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -0,0 +1,48 @@ +/** + * @class + * @private + */ +function WebGLGraphicsData(gl) { + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +} + +WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; +module.exports = WebGLGraphicsData; + +/** + * + */ +WebGLGraphicsData.prototype.reset = function () { + this.points = []; + this.indices = []; +}; + +/** + * + */ +WebGLGraphicsData.prototype.upload = function () { + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; diff --git a/src/core/index.js b/src/core/index.js index 6f744e9..1d0a398 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -23,12 +23,12 @@ Sprite: require('./sprites/Sprite'), SpriteBatch: require('./sprites/SpriteBatch'), - SpriteRenderer: require('./sprites/SpriteRenderer'), + SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), // primitives - Graphics: require('./primitives/Graphics'), - GraphicsData: require('./primitives/GraphicsData'), - GraphicsRenderer: require('./primitives/GraphicsRenderer'), + Graphics: require('./graphics/Graphics'), + GraphicsData: require('./graphics/GraphicsData'), + GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,10 +42,8 @@ CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), // renderers - webgl - WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), - // WebGLGraphics: require('./renderers/webgl/utils/WebGLGraphics'), - - + WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), + Shader: require('./renderers/webgl/shaders/Shader'), /** * This helper function will automatically detect which renderer you should be using. @@ -103,7 +101,3 @@ return core.autoDetectRenderer(width, height, options, isAndroid); } }; - -// Adding here for now.. sure there is a better place! -core.WebGLRenderer.registerObjectRenderer('sprite', core.SpriteRenderer); -core.WebGLRenderer.registerObjectRenderer('graphics', core.GraphicsRenderer); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index a086950..81de05c 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -268,6 +268,6 @@ matrix.ty = this.ty; return matrix; -} +}; Matrix.IDENTITY = new Matrix(); diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js deleted file mode 100644 index 8db9b45..0000000 --- a/src/core/primitives/Graphics.js +++ /dev/null @@ -1,1116 +0,0 @@ -var DisplayObjectContainer = require('../display/DisplayObjectContainer'), - //Sprite = require('../display/Sprite'), - Texture = require('../textures/Texture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), - // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), - GraphicsData = require('./GraphicsData'), - math = require('../math'), - CONST = require('../const'); - -/** - * The Graphics class contains methods used to draw primitive shapes such as lines, circles and - * rectangles to the display, and color and fill them. - * - * @class - * @extends DisplayObjectContainer - * @namespace PIXI - */ -function Graphics() -{ - DisplayObjectContainer.call(this); - - this.renderable = true; - - /** - * The alpha value used when filling the Graphics object. - * - * @member {number} - * @default 1 - */ - this.fillAlpha = 1; - - /** - * The width (thickness) of any lines drawn. - * - * @member {number} - * @default 0 - */ - this.lineWidth = 0; - - /** - * The color of any lines drawn. - * - * @member {string} - * @default 0 - */ - this.lineColor = 0; - - /** - * Graphics data - * - * @member {GraphicsData[]} - * @private - */ - this.graphicsData = []; - - /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * Current path - * - * @member {GraphicsData} - * @private - */ - this.currentPath = null; - - /** - * Array containing some WebGL-related properties used by the WebGL renderer. - * - * @member {object} - * @private - */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; - - /** - * Whether this shape is being used as a mask. - * - * @member {boolean} - */ - this.isMask = false; - - /** - * The bounds' padding used for bounds calculation. - * - * @member {number} - */ - this.boundsPadding = 0; - - /** - * A cache of the local bounds to prevent recalculation. - * - * @member {Rectangle} - * @private - */ - this._localBounds = new math.Rectangle(0,0,1,1); - - /** - * Used to detect if the graphics object has changed. If this is set to true then the graphics - * object will be recalculated. - * - * @member {boolean} - * @private - */ - this.dirty = true; - - /** - * Used to detect if the WebGL graphics object has changed. If this is set to true then the - * graphics object will be recalculated. - * - * @member {boolean} - * @private - */ - this.glDirty = false; - - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; -} - -// constructor -Graphics.prototype = Object.create(DisplayObjectContainer.prototype); -Graphics.prototype.constructor = Graphics; -module.exports = Graphics; - -Object.defineProperties(Graphics.prototype, { - /** - * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. - * This is useful if your graphics element does not change often, as it will speed up the rendering - * of the object in exchange for taking up texture memory. It is also useful if you need the graphics - * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if - * you are constantly redrawing the graphics element. - * - * @member {boolean} - * @memberof Graphics# - * @default false - * @private - */ - cacheAsBitmap: { - get: function () - { - return this._cacheAsBitmap; - }, - set: function (value) - { - this._cacheAsBitmap = value; - - if (this._cacheAsBitmap) - { - this._generateCachedSprite(); - } - else - { - this.destroyCachedSprite(); - this.dirty = true; - } - } - } -}); - -/** - * Creates a new Graphics object with the same values as this one. - * - * @return {Graphics} - */ -GraphicsData.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = this.dirty; - clone.glDirty = this.glDirty; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData.clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {Graphics} - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (arguments.length < 3) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); - } - else - { - // otherwise its empty so lets just set the line properties - this.currentPath.lineWidth = this.lineWidth; - this.currentPath.lineColor = this.lineColor; - this.currentPath.lineAlpha = this.lineAlpha; - } - } - - return this; -}; - -/** - * Moves the current drawing position to x, y. - * - * @param x {number} the X coordinate to move to - * @param y {number} the Y coordinate to move to - * @return {Graphics} - */ -Graphics.prototype.moveTo = function (x, y) -{ - this.drawShape(new math.Polygon([x,y])); - - return this; -}; - -/** - * Draws a line using the current line style from the current drawing position to (x, y); - * The current drawing position is then set to (x, y). - * - * @param x {number} the X coordinate to draw to - * @param y {number} the Y coordinate to draw to - * @return {Graphics} - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a quadratic bezier curve and then draws it. - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @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 {Graphics} - */ -Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var xa, - ya, - n = 20, - points = this.currentPath.shape.points; - - if (points.length === 0) - { - this.moveTo(0, 0); - } - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - for (var i = 1; i <= n; ++i) - { - j = i / n; - - xa = fromX + ( (cpX - fromX) * j ); - ya = fromY + ( (cpY - fromY) * j ); - - points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), - ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); - } - - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a bezier curve and then draws it. - * - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param cpX2 {number} Second Control point x - * @param cpY2 {number} Second Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {Graphics} - */ -Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var n = 20, - dt, - dt2, - dt3, - t2, - t3, - points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - - for (var i = 1; i <= n; ++i) - { - j = i / n; - - dt = (1 - j); - dt2 = dt * dt; - dt3 = dt2 * dt; - - t2 = j * j; - t3 = t2 * j; - - points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, - dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); - } - - this.dirty = true; - - return this; -}; - -/** - * The arcTo() method creates an arc/curve between two tangents on the canvas. - * - * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! - * - * @param x1 {number} The x-coordinate of the beginning of the arc - * @param y1 {number} The y-coordinate of the beginning of the arc - * @param x2 {number} The x-coordinate of the end of the arc - * @param y2 {number} The y-coordinate of the end of the arc - * @param radius {number} The radius of the arc - * @return {Graphics} - */ -Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points.push(x1, y1); - } - } - else - { - this.moveTo(x1, y1); - } - - var points = this.currentPath.shape.points, - fromX = points[points.length-2], - fromY = points[points.length-1], - a1 = fromY - y1, - b1 = fromX - x1, - a2 = y2 - y1, - b2 = x2 - x1, - mm = Math.abs(a1 * b2 - b1 * a2); - - if (mm < 1.0e-8 || radius === 0) - { - if (points[points.length-2] !== x1 || points[points.length-1] !== y1) - { - points.push(x1, y1); - } - } - else - { - var dd = a1 * a1 + b1 * b1, - cc = a2 * a2 + b2 * b2, - tt = a1 * a2 + b1 * b2, - k1 = radius * Math.sqrt(dd) / mm, - k2 = radius * Math.sqrt(cc) / mm, - j1 = k1 * tt / dd, - j2 = k2 * tt / cc, - cx = k1 * b2 + k2 * b1, - cy = k1 * a2 + k2 * a1, - px = b1 * (k2 + j1), - py = a1 * (k2 + j1), - qx = b2 * (k1 + j2), - qy = a2 * (k1 + j2), - startAngle = Math.atan2(py - cy, px - cx), - endAngle = Math.atan2(qy - cy, qx - cx); - - this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); - } - - this.dirty = true; - - return this; -}; - -/** - * The arc method creates an arc/curve (used to create circles, or parts of circles). - * - * @param cx {number} The x-coordinate of the center of the circle - * @param cy {number} The y-coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) - * @param endAngle {number} The ending angle, in radians - * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {Graphics} - */ -Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - var points; - - // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... - if (this.currentPath) - { - points = this.currentPath.shape.points; - - if (points.length === 0) - { - points.push(startX, startY); - } - else if (points[points.length-2] !== startX || points[points.length-1] !== startY) - { - points.push(startX, startY); - } - } - else - { - this.moveTo(startX, startY); - points = this.currentPath.shape.points; - } - - if (startAngle === endAngle) - { - return this; - } - - if (!anticlockwise && endAngle <= startAngle) - { - endAngle += Math.PI * 2; - } - else if (anticlockwise && startAngle <= endAngle) - { - startAngle += Math.PI * 2; - } - - var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); - var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; - - if (sweep === 0) - { - return this; - } - - var theta = sweep/(segs*2); - var theta2 = theta*2; - - var cTheta = Math.cos(theta); - var sTheta = Math.sin(theta); - - var segMinus = segs - 1; - - var remainder = ( segMinus % 1 ) / segMinus; - - for (var i = 0; i <= segMinus; ++i) - { - var real = i + remainder * i; - var angle = ((theta) + startAngle + (theta2 * real)); - - var c = Math.cos(angle); - var s = -Math.sin(angle); - - points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, - ( (cTheta * -s) + (sTheta * c) ) * radius + cy); - } - - this.dirty = true; - - return this; -}; - -/** - * Specifies a simple one-color fill that subsequent calls to other Graphics methods - * (such as lineTo() or drawCircle()) use when drawing. - * - * @param color {number} the color of the fill - * @param alpha {number} the alpha of the fill - * @return {Graphics} - */ -Graphics.prototype.beginFill = function (color, alpha) -{ - this.filling = true; - this.fillColor = color || 0; - this.fillAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length <= 2) - { - this.currentPath.fill = this.filling; - this.currentPath.fillColor = this.fillColor; - this.currentPath.fillAlpha = this.fillAlpha; - } - } - return this; -}; - -/** - * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. - * - * @return {Graphics} - */ -Graphics.prototype.endFill = function () -{ - this.filling = false; - this.fillColor = null; - this.fillAlpha = 1; - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @return {Graphics} - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {Graphics} - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {Graphics} - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {Array} The path data used to construct the polygon. - * @return {Graphics} - */ -Graphics.prototype.drawPolygon = function (path) -{ - if (!(path instanceof Array)) - { - path = Array.prototype.slice.call(arguments); - } - - this.drawShape(new math.Polygon(path)); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {Graphics} - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty = true; - this.clearDirty = true; - this.graphicsData = []; - - return this; -}; - -/** - * Useful function that returns a texture of the graphics object that can then be used to create sprites - * This can be quite useful if your geometry is complicated and needs to be reused multiple times. - * - * @param resolution {number} The resolution of the texture being generated - * @param scaleMode {number} Should be one of the scaleMode consts - * @return {Texture} a texture of the graphics object - */ -Graphics.prototype.generateTexture = function (resolution, scaleMode) -{ - resolution = resolution || 1; - - var bounds = this.getBounds(); - - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); - - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - canvasBuffer.context.scale(resolution, resolution); - - canvasBuffer.context.translate(-bounds.x,-bounds.y); - - CanvasGraphics.renderGraphics(this, canvasBuffer.context); - - return texture; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (this.isMask === true) - { - return; - } - - // this code may still be needed so leaving for now.. - // - /* - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture on the gpu too! - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.worldAlpha = this.worldAlpha; - - Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); - - return; - } - - */ - - if (this.glDirty) - { - this.dirty = true; - this.glDirty = false; - } - - renderer.setObjectRenderer(renderer.objectRenderers.graphics); - renderer.objectRenderers.graphics.render(this); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} - * @private - */ -Graphics.prototype.renderCanvas = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.alpha <= 0 || this.isMask === true) - { - return; - } - - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.alpha = this.alpha; - - Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); - - return; - } - else - { - var context = renderer.context; - var transform = this.worldTransform; - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - var resolution = renderer.resolution; - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } - } -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @return {Rectangle} the rectangular bounding area - */ -Graphics.prototype.getBounds = function (matrix) -{ - // return an empty object if the item is a mask! - if (this.isMask) - { - return math.Rectangle.EMPTY; - } - - if (this.dirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.dirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - return this._bounds; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; - - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; -}; - -/** - * Generates the cached sprite when the sprite has cacheAsBitmap = true - * - * @private - */ -Graphics.prototype._generateCachedSprite = function () -{ - var bounds = this.getLocalBounds(); - - if (!this._cachedSprite) - { - var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); - var texture = Texture.fromCanvas(canvasBuffer.canvas); - - this._cachedSprite = new Sprite(texture); - this._cachedSprite.buffer = canvasBuffer; - - this._cachedSprite.worldTransform = this.worldTransform; - } - else - { - this._cachedSprite.buffer.resize(bounds.width, bounds.height); - } - - // leverage the anchor to account for the offset of the element - this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); - this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); - - // this._cachedSprite.buffer.context.save(); - this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); - - // make sure we set the alpha of the graphics to 1 for the render.. - this.worldAlpha = 1; - - // now render the graphic.. - CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); - - this._cachedSprite.alpha = this.alpha; -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Graphics.prototype.updateCachedSpriteTexture = function () -{ - var cachedSprite = this._cachedSprite; - var texture = cachedSprite.texture; - var canvas = cachedSprite.buffer.canvas; - - texture.baseTexture.width = canvas.width; - texture.baseTexture.height = canvas.height; - texture.crop.width = texture.frame.width = canvas.width; - texture.crop.height = texture.frame.height = canvas.height; - - cachedSprite._width = canvas.width; - cachedSprite._height = canvas.height; - - // update the dirty base textures - texture.baseTexture.dirty(); -}; - -/** - * Destroys a previous cached sprite. - * - */ -Graphics.prototype.destroyCachedSprite = function () -{ - this._cachedSprite.texture.destroy(true); - - // let the gc collect the unused sprite - // TODO could be object pooled! - this._cachedSprite = null; -}; - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. - * @return {GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = this.filling; - this.currentPath = data; - } - - this.dirty = true; - - return data; -}; diff --git a/src/core/primitives/GraphicsData.js b/src/core/primitives/GraphicsData.js deleted file mode 100644 index 788f6f2..0000000 --- a/src/core/primitives/GraphicsData.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * A GraphicsData object. - * - * @class - * @namespace PIXI - */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - this.lineWidth = lineWidth; - this.lineColor = lineColor; - this.lineAlpha = lineAlpha; - this._lineTint = lineColor; - - this.fillColor = fillColor; - this.fillAlpha = fillAlpha; - this._fillTint = fillColor; - this.fill = fill; - - this.shape = shape; - this.type = shape.type; -} - -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {GraphicsData} - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js deleted file mode 100644 index 1a508f9..0000000 --- a/src/core/primitives/GraphicsRenderer.js +++ /dev/null @@ -1,874 +0,0 @@ -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; - -/** - * 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 ); - -// var matrix = graphics.worldTransform.clone(); -// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); -// matrix.append(graphics.worldTransform); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.maskManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.maskManager.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.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); - - 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/primitives/WebGLGraphicsData.js b/src/core/primitives/WebGLGraphicsData.js deleted file mode 100755 index 41be102..0000000 --- a/src/core/primitives/WebGLGraphicsData.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @class - * @private - */ -function WebGLGraphicsData(gl) { - this.gl = gl; - - //TODO does this need to be split before uploding?? - this.color = [0,0,0]; // color split! - this.points = []; - this.indices = []; - this.buffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - this.mode = 1; - this.alpha = 1; - this.dirty = true; -} - -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; -module.exports = WebGLGraphicsData; - -/** - * - */ -WebGLGraphicsData.prototype.reset = function () { - this.points = []; - this.indices = []; -}; - -/** - * - */ -WebGLGraphicsData.prototype.upload = function () { - var gl = this.gl; - -// this.lastIndex = graphics.graphicsData.length; - this.glPoints = new Float32Array(this.points); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); - - this.glIndicies = new Uint16Array(this.indices); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); - - this.dirty = false; -}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6c1fb7d..4e82baa 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -174,6 +174,9 @@ // time to create the render managers! each one focuses on managing a state in webGL + // initialize the context so it is ready for the managers. + this._initContext(); + /** * Deals with managing the shader programs and their attribs * @member {WebGLShaderManager} @@ -204,17 +207,11 @@ */ this.blendModeManager = new WebGLBlendModeManager(this); - - this.blendModes = null; this._boundUpdateTexture = this.updateTexture.bind(this); this._boundDestroyTexture = this.destroyTexture.bind(this); - - - // time init the context.. - this._initContext(); this.currentRenderTarget = this.renderTarget; @@ -228,27 +225,18 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - /** - * Manages the renderer of specific objects. - * - * @member {object} - */ - this.objectRenderers = {}; - this.currentRenderer = new ObjectRenderer(); - - // create an instance of each registered object renderer - for (var o in WebGLRenderer._objectRenderers) { - this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); - } - + this.initPlugins(); } // constructor WebGLRenderer.prototype.constructor = WebGLRenderer; module.exports = WebGLRenderer; +WebGLRenderer.glContextId = 0; + +utils.pluginTarget.mixin(WebGLRenderer); utils.eventTarget.mixin(WebGLRenderer.prototype); Object.defineProperties(WebGLRenderer.prototype, { @@ -297,11 +285,9 @@ gl.enable(gl.BLEND); this.renderTarget = new RenderTarget(this.gl, this.width, this.height, null, true); - this.emit('context', gl); - // now resize and we are good to go! this.resize(this.width, this.height); }; @@ -349,7 +335,7 @@ gl.clear(gl.COLOR_BUFFER_BIT); } - this.renderDisplayObject(object, this.renderTarget)//this.projection); + this.renderDisplayObject(object, this.renderTarget);//this.projection); }; /** @@ -536,12 +522,7 @@ this.maskManager.destroy(); this.filterManager.destroy(); - for (var o in this.objectRenderers) { - this.objectRenderers[o].destroy(); - this.objectRenderers[o] = null; - } - - this.objectRenderers = null; + this.destroyPlugins(); // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -611,11 +592,3 @@ this.blendModes[CONST.blendModes.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; } }; - -WebGLRenderer.glContextId = 0; -WebGLRenderer._objectRenderers = {}; - -WebGLRenderer.registerObjectRenderer = function (name, ctor) { - WebGLRenderer._objectRenderers[name] = ctor; -}; - diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js index 87c99f0..d6ed3c6 100644 --- a/src/core/renderers/webgl/managers/WebGLFilterManager.js +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -51,7 +51,7 @@ */ WebGLFilterManager.prototype.begin = function (buffer) { - this.defaultShader = this.renderer.shaderManager.defaultShader; + this.defaultShader = this.renderer.shaderManager.plugins.defaultShader; this.width = this.renderer.projection.x * 2; this.height = -this.renderer.projection.y * 2; @@ -385,7 +385,7 @@ if (!shader) { - shader = new Shader(gl); + shader = new Shader(this.renderer.shaderManager); shader.fragmentSrc = filter.fragmentSrc; shader.uniforms = filter.uniforms; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js index 6a1f782..d6a11c1 100644 --- a/src/core/renderers/webgl/managers/WebGLMaskManager.js +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -126,11 +126,11 @@ // bind the graphics object.. var projection = this.renderer.projection, offset = this.renderer.offset, - shader;// = this.renderer.shaderManager.primitiveShader; + shader;// = this.renderer.shaderManager.plugins.primitiveShader; if (webGLData.mode === 1) { - shader = this.renderer.shaderManager.complexPrimitiveShader; + shader = this.renderer.shaderManager.plugins.complexPrimitiveShader; this.renderer.shaderManager.setShader(shader); @@ -156,7 +156,7 @@ else { //this.renderer.shaderManager.activatePrimitiveShader(); - shader = this.renderer.shaderManager.primitiveShader; + shader = this.renderer.shaderManager.plugins.primitiveShader; this.renderer.shaderManager.setShader( shader ); gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); @@ -292,11 +292,11 @@ */ WebGLMaskManager.prototype.pushMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); if (maskData.dirty) { - this.renderer.objectRenderers.graphics.updateGraphics(maskData, this.renderer.gl); + this.renderer.plugins.graphics.updateGraphics(maskData, this.renderer.gl); } if (!maskData._webGL[this.renderer.gl.id].data.length) @@ -314,7 +314,7 @@ */ WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); this.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); }; diff --git a/src/core/renderers/webgl/managers/WebGLShaderManager.js b/src/core/renderers/webgl/managers/WebGLShaderManager.js index 90c6477..9ebb58e 100644 --- a/src/core/renderers/webgl/managers/WebGLShaderManager.js +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -1,9 +1,5 @@ var WebGLManager = require('./WebGLManager'), - PrimitiveShader = require('../shaders/PrimitiveShader'), - ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), - Shader = require('../shaders/Shader'), - FastShader = require('../shaders/FastShader'), - StripShader = require('../shaders/StripShader'); + utils = require('../../../utils'); /** * @class @@ -51,41 +47,16 @@ */ this.currentShader = null; - // this shader is used for rendering primitives - this.primitiveShader = null; - - // this shader is used for rendering triangle strips - this.complexPrimitiveShader = null; - - // this shader is used for the default sprite rendering - this.defaultShader = null; - - // this shader is used for the fast sprite rendering - this.fastShader = null; - - // the next one is used for rendering triangle strips - this.stripShader = null; + this.initPlugins(); // listen for context and update necessary shaders var self = this; - this.renderer.on('context', function (event) + this.renderer.on('context', function () { - var gl = event.data; - - // this shader is used for rendering primitives - self.primitiveShader = new PrimitiveShader(gl); - - // this shader is used for rendering triangle strips - self.complexPrimitiveShader = new ComplexPrimitiveShader(gl); - - // this shader is used for the default sprite rendering - self.defaultShader = new Shader(gl); - - // this shader is used for the fast sprite rendering - self.fastShader = new FastShader(gl); - - // the next one is used for rendering triangle strips - self.stripShader = new StripShader(gl); + for (var o in this.plugins) + { + this.plugins[o] = new (this.plugins[o].constructor)(self); + } self.setShader(self.defaultShader); }); @@ -95,6 +66,8 @@ WebGLShaderManager.prototype.constructor = WebGLShaderManager; module.exports = WebGLShaderManager; +utils.pluginTarget.mixin(WebGLShaderManager); + /** * Takes the attributes given in parameters. * @@ -164,24 +137,11 @@ */ WebGLShaderManager.prototype.destroy = function () { + this.destroyPlugins(); + this.attribState = null; this.tempAttribState = null; - this.primitiveShader.destroy(); - this.primitiveShader = null; - - this.complexPrimitiveShader.destroy(); - this.complexPrimitiveShader = null; - - this.defaultShader.destroy(); - this.defaultShader = null; - - this.fastShader.destroy(); - this.fastShader = null; - - this.stripShader.destroy(); - this.stripShader = null; - this.renderer = null; }; diff --git a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js b/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js deleted file mode 100644 index f8a07da..0000000 --- a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js +++ /dev/null @@ -1,53 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function ComplexPrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform vec3 tint;', - 'uniform float alpha;', - 'uniform vec3 color;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - color: { type: '3f', value: [0,0,0] }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); -ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; -module.exports = ComplexPrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/FastShader.js b/src/core/renderers/webgl/shaders/FastShader.js index d198f10..c80d040 100644 --- a/src/core/renderers/webgl/shaders/FastShader.js +++ b/src/core/renderers/webgl/shaders/FastShader.js @@ -1,15 +1,16 @@ -var Shader = require('./Shader'); +var Shader = require('./Shader'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @extends Shader * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. */ -function FastShader(gl) +function FastShader(shaderManager) { Shader.call(this, - gl, + shaderManager, // vertex shader [ 'attribute vec2 aVertexPosition;', @@ -70,3 +71,5 @@ FastShader.prototype = Object.create(Shader.prototype); FastShader.prototype.constructor = FastShader; module.exports = FastShader; + +WebGLShaderManager.registerPlugin('fastShader', FastShader); diff --git a/src/core/renderers/webgl/shaders/PrimitiveShader.js b/src/core/renderers/webgl/shaders/PrimitiveShader.js deleted file mode 100644 index 753c475..0000000 --- a/src/core/renderers/webgl/shaders/PrimitiveShader.js +++ /dev/null @@ -1,55 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform float alpha;', - 'uniform float flipY;', - 'uniform vec3 tint;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aColor:0 - } - ); -} - -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; -module.exports = PrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/Shader.js b/src/core/renderers/webgl/shaders/Shader.js index d065a76..c60c5f9 100644 --- a/src/core/renderers/webgl/shaders/Shader.js +++ b/src/core/renderers/webgl/shaders/Shader.js @@ -1,12 +1,16 @@ -var utils = require('../../../utils'); +var utils = require('../../../utils'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @namespace PIXI - * @param [fragmentSrc] {string} The source of the fragment shader. + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. * @param [vertexSrc] {string} The source of the vertex shader. + * @param [fragmentSrc] {string} The source of the fragment shader. + * @param customUniforms {object} Custom uniforms to use to augment the built-in ones. + * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Shader(gl, vertexSrc, fragmentSrc, customUniforms, customAttributes) +function Shader(shaderManager, vertexSrc, fragmentSrc, customUniforms, customAttributes) { /** * @member {number} @@ -18,7 +22,7 @@ * @member {WebGLContext} * @readonly */ - this.gl = gl; + this.gl = shaderManager.renderer.gl; /** * The WebGL program. @@ -31,8 +35,8 @@ uSampler: { type: 'sampler2D', value: 0 }, projectionVector: { type: '2f', value: { x: 0, y: 0 } }, offsetVector: { type: '2f', value: { x: 0, y: 0 } }, - projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, - 0, 1, 0, + projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, + 0, 1, 0, 0, 0, 1) }, dimensions: { type: '4f', value: new Float32Array(4) } }; @@ -103,6 +107,8 @@ Shader.prototype.constructor = Shader; module.exports = Shader; +WebGLShaderManager.registerPlugin('defaultShader', Shader); + Shader.prototype.init = function () { this.compile(); @@ -506,4 +512,4 @@ } return shader; -}; \ No newline at end of file +}; diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js new file mode 100644 index 0000000..41be102 --- /dev/null +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -0,0 +1,48 @@ +/** + * @class + * @private + */ +function WebGLGraphicsData(gl) { + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +} + +WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; +module.exports = WebGLGraphicsData; + +/** + * + */ +WebGLGraphicsData.prototype.reset = function () { + this.points = []; + this.indices = []; +}; + +/** + * + */ +WebGLGraphicsData.prototype.upload = function () { + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; diff --git a/src/core/index.js b/src/core/index.js index 6f744e9..1d0a398 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -23,12 +23,12 @@ Sprite: require('./sprites/Sprite'), SpriteBatch: require('./sprites/SpriteBatch'), - SpriteRenderer: require('./sprites/SpriteRenderer'), + SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), // primitives - Graphics: require('./primitives/Graphics'), - GraphicsData: require('./primitives/GraphicsData'), - GraphicsRenderer: require('./primitives/GraphicsRenderer'), + Graphics: require('./graphics/Graphics'), + GraphicsData: require('./graphics/GraphicsData'), + GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,10 +42,8 @@ CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), // renderers - webgl - WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), - // WebGLGraphics: require('./renderers/webgl/utils/WebGLGraphics'), - - + WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), + Shader: require('./renderers/webgl/shaders/Shader'), /** * This helper function will automatically detect which renderer you should be using. @@ -103,7 +101,3 @@ return core.autoDetectRenderer(width, height, options, isAndroid); } }; - -// Adding here for now.. sure there is a better place! -core.WebGLRenderer.registerObjectRenderer('sprite', core.SpriteRenderer); -core.WebGLRenderer.registerObjectRenderer('graphics', core.GraphicsRenderer); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index a086950..81de05c 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -268,6 +268,6 @@ matrix.ty = this.ty; return matrix; -} +}; Matrix.IDENTITY = new Matrix(); diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js deleted file mode 100644 index 8db9b45..0000000 --- a/src/core/primitives/Graphics.js +++ /dev/null @@ -1,1116 +0,0 @@ -var DisplayObjectContainer = require('../display/DisplayObjectContainer'), - //Sprite = require('../display/Sprite'), - Texture = require('../textures/Texture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), - // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), - GraphicsData = require('./GraphicsData'), - math = require('../math'), - CONST = require('../const'); - -/** - * The Graphics class contains methods used to draw primitive shapes such as lines, circles and - * rectangles to the display, and color and fill them. - * - * @class - * @extends DisplayObjectContainer - * @namespace PIXI - */ -function Graphics() -{ - DisplayObjectContainer.call(this); - - this.renderable = true; - - /** - * The alpha value used when filling the Graphics object. - * - * @member {number} - * @default 1 - */ - this.fillAlpha = 1; - - /** - * The width (thickness) of any lines drawn. - * - * @member {number} - * @default 0 - */ - this.lineWidth = 0; - - /** - * The color of any lines drawn. - * - * @member {string} - * @default 0 - */ - this.lineColor = 0; - - /** - * Graphics data - * - * @member {GraphicsData[]} - * @private - */ - this.graphicsData = []; - - /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * Current path - * - * @member {GraphicsData} - * @private - */ - this.currentPath = null; - - /** - * Array containing some WebGL-related properties used by the WebGL renderer. - * - * @member {object} - * @private - */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; - - /** - * Whether this shape is being used as a mask. - * - * @member {boolean} - */ - this.isMask = false; - - /** - * The bounds' padding used for bounds calculation. - * - * @member {number} - */ - this.boundsPadding = 0; - - /** - * A cache of the local bounds to prevent recalculation. - * - * @member {Rectangle} - * @private - */ - this._localBounds = new math.Rectangle(0,0,1,1); - - /** - * Used to detect if the graphics object has changed. If this is set to true then the graphics - * object will be recalculated. - * - * @member {boolean} - * @private - */ - this.dirty = true; - - /** - * Used to detect if the WebGL graphics object has changed. If this is set to true then the - * graphics object will be recalculated. - * - * @member {boolean} - * @private - */ - this.glDirty = false; - - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; -} - -// constructor -Graphics.prototype = Object.create(DisplayObjectContainer.prototype); -Graphics.prototype.constructor = Graphics; -module.exports = Graphics; - -Object.defineProperties(Graphics.prototype, { - /** - * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. - * This is useful if your graphics element does not change often, as it will speed up the rendering - * of the object in exchange for taking up texture memory. It is also useful if you need the graphics - * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if - * you are constantly redrawing the graphics element. - * - * @member {boolean} - * @memberof Graphics# - * @default false - * @private - */ - cacheAsBitmap: { - get: function () - { - return this._cacheAsBitmap; - }, - set: function (value) - { - this._cacheAsBitmap = value; - - if (this._cacheAsBitmap) - { - this._generateCachedSprite(); - } - else - { - this.destroyCachedSprite(); - this.dirty = true; - } - } - } -}); - -/** - * Creates a new Graphics object with the same values as this one. - * - * @return {Graphics} - */ -GraphicsData.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = this.dirty; - clone.glDirty = this.glDirty; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData.clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {Graphics} - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (arguments.length < 3) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); - } - else - { - // otherwise its empty so lets just set the line properties - this.currentPath.lineWidth = this.lineWidth; - this.currentPath.lineColor = this.lineColor; - this.currentPath.lineAlpha = this.lineAlpha; - } - } - - return this; -}; - -/** - * Moves the current drawing position to x, y. - * - * @param x {number} the X coordinate to move to - * @param y {number} the Y coordinate to move to - * @return {Graphics} - */ -Graphics.prototype.moveTo = function (x, y) -{ - this.drawShape(new math.Polygon([x,y])); - - return this; -}; - -/** - * Draws a line using the current line style from the current drawing position to (x, y); - * The current drawing position is then set to (x, y). - * - * @param x {number} the X coordinate to draw to - * @param y {number} the Y coordinate to draw to - * @return {Graphics} - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a quadratic bezier curve and then draws it. - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @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 {Graphics} - */ -Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var xa, - ya, - n = 20, - points = this.currentPath.shape.points; - - if (points.length === 0) - { - this.moveTo(0, 0); - } - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - for (var i = 1; i <= n; ++i) - { - j = i / n; - - xa = fromX + ( (cpX - fromX) * j ); - ya = fromY + ( (cpY - fromY) * j ); - - points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), - ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); - } - - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a bezier curve and then draws it. - * - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param cpX2 {number} Second Control point x - * @param cpY2 {number} Second Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {Graphics} - */ -Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var n = 20, - dt, - dt2, - dt3, - t2, - t3, - points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - - for (var i = 1; i <= n; ++i) - { - j = i / n; - - dt = (1 - j); - dt2 = dt * dt; - dt3 = dt2 * dt; - - t2 = j * j; - t3 = t2 * j; - - points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, - dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); - } - - this.dirty = true; - - return this; -}; - -/** - * The arcTo() method creates an arc/curve between two tangents on the canvas. - * - * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! - * - * @param x1 {number} The x-coordinate of the beginning of the arc - * @param y1 {number} The y-coordinate of the beginning of the arc - * @param x2 {number} The x-coordinate of the end of the arc - * @param y2 {number} The y-coordinate of the end of the arc - * @param radius {number} The radius of the arc - * @return {Graphics} - */ -Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points.push(x1, y1); - } - } - else - { - this.moveTo(x1, y1); - } - - var points = this.currentPath.shape.points, - fromX = points[points.length-2], - fromY = points[points.length-1], - a1 = fromY - y1, - b1 = fromX - x1, - a2 = y2 - y1, - b2 = x2 - x1, - mm = Math.abs(a1 * b2 - b1 * a2); - - if (mm < 1.0e-8 || radius === 0) - { - if (points[points.length-2] !== x1 || points[points.length-1] !== y1) - { - points.push(x1, y1); - } - } - else - { - var dd = a1 * a1 + b1 * b1, - cc = a2 * a2 + b2 * b2, - tt = a1 * a2 + b1 * b2, - k1 = radius * Math.sqrt(dd) / mm, - k2 = radius * Math.sqrt(cc) / mm, - j1 = k1 * tt / dd, - j2 = k2 * tt / cc, - cx = k1 * b2 + k2 * b1, - cy = k1 * a2 + k2 * a1, - px = b1 * (k2 + j1), - py = a1 * (k2 + j1), - qx = b2 * (k1 + j2), - qy = a2 * (k1 + j2), - startAngle = Math.atan2(py - cy, px - cx), - endAngle = Math.atan2(qy - cy, qx - cx); - - this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); - } - - this.dirty = true; - - return this; -}; - -/** - * The arc method creates an arc/curve (used to create circles, or parts of circles). - * - * @param cx {number} The x-coordinate of the center of the circle - * @param cy {number} The y-coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) - * @param endAngle {number} The ending angle, in radians - * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {Graphics} - */ -Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - var points; - - // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... - if (this.currentPath) - { - points = this.currentPath.shape.points; - - if (points.length === 0) - { - points.push(startX, startY); - } - else if (points[points.length-2] !== startX || points[points.length-1] !== startY) - { - points.push(startX, startY); - } - } - else - { - this.moveTo(startX, startY); - points = this.currentPath.shape.points; - } - - if (startAngle === endAngle) - { - return this; - } - - if (!anticlockwise && endAngle <= startAngle) - { - endAngle += Math.PI * 2; - } - else if (anticlockwise && startAngle <= endAngle) - { - startAngle += Math.PI * 2; - } - - var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); - var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; - - if (sweep === 0) - { - return this; - } - - var theta = sweep/(segs*2); - var theta2 = theta*2; - - var cTheta = Math.cos(theta); - var sTheta = Math.sin(theta); - - var segMinus = segs - 1; - - var remainder = ( segMinus % 1 ) / segMinus; - - for (var i = 0; i <= segMinus; ++i) - { - var real = i + remainder * i; - var angle = ((theta) + startAngle + (theta2 * real)); - - var c = Math.cos(angle); - var s = -Math.sin(angle); - - points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, - ( (cTheta * -s) + (sTheta * c) ) * radius + cy); - } - - this.dirty = true; - - return this; -}; - -/** - * Specifies a simple one-color fill that subsequent calls to other Graphics methods - * (such as lineTo() or drawCircle()) use when drawing. - * - * @param color {number} the color of the fill - * @param alpha {number} the alpha of the fill - * @return {Graphics} - */ -Graphics.prototype.beginFill = function (color, alpha) -{ - this.filling = true; - this.fillColor = color || 0; - this.fillAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length <= 2) - { - this.currentPath.fill = this.filling; - this.currentPath.fillColor = this.fillColor; - this.currentPath.fillAlpha = this.fillAlpha; - } - } - return this; -}; - -/** - * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. - * - * @return {Graphics} - */ -Graphics.prototype.endFill = function () -{ - this.filling = false; - this.fillColor = null; - this.fillAlpha = 1; - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @return {Graphics} - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {Graphics} - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {Graphics} - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {Array} The path data used to construct the polygon. - * @return {Graphics} - */ -Graphics.prototype.drawPolygon = function (path) -{ - if (!(path instanceof Array)) - { - path = Array.prototype.slice.call(arguments); - } - - this.drawShape(new math.Polygon(path)); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {Graphics} - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty = true; - this.clearDirty = true; - this.graphicsData = []; - - return this; -}; - -/** - * Useful function that returns a texture of the graphics object that can then be used to create sprites - * This can be quite useful if your geometry is complicated and needs to be reused multiple times. - * - * @param resolution {number} The resolution of the texture being generated - * @param scaleMode {number} Should be one of the scaleMode consts - * @return {Texture} a texture of the graphics object - */ -Graphics.prototype.generateTexture = function (resolution, scaleMode) -{ - resolution = resolution || 1; - - var bounds = this.getBounds(); - - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); - - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - canvasBuffer.context.scale(resolution, resolution); - - canvasBuffer.context.translate(-bounds.x,-bounds.y); - - CanvasGraphics.renderGraphics(this, canvasBuffer.context); - - return texture; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (this.isMask === true) - { - return; - } - - // this code may still be needed so leaving for now.. - // - /* - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture on the gpu too! - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.worldAlpha = this.worldAlpha; - - Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); - - return; - } - - */ - - if (this.glDirty) - { - this.dirty = true; - this.glDirty = false; - } - - renderer.setObjectRenderer(renderer.objectRenderers.graphics); - renderer.objectRenderers.graphics.render(this); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} - * @private - */ -Graphics.prototype.renderCanvas = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.alpha <= 0 || this.isMask === true) - { - return; - } - - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.alpha = this.alpha; - - Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); - - return; - } - else - { - var context = renderer.context; - var transform = this.worldTransform; - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - var resolution = renderer.resolution; - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } - } -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @return {Rectangle} the rectangular bounding area - */ -Graphics.prototype.getBounds = function (matrix) -{ - // return an empty object if the item is a mask! - if (this.isMask) - { - return math.Rectangle.EMPTY; - } - - if (this.dirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.dirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - return this._bounds; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; - - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; -}; - -/** - * Generates the cached sprite when the sprite has cacheAsBitmap = true - * - * @private - */ -Graphics.prototype._generateCachedSprite = function () -{ - var bounds = this.getLocalBounds(); - - if (!this._cachedSprite) - { - var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); - var texture = Texture.fromCanvas(canvasBuffer.canvas); - - this._cachedSprite = new Sprite(texture); - this._cachedSprite.buffer = canvasBuffer; - - this._cachedSprite.worldTransform = this.worldTransform; - } - else - { - this._cachedSprite.buffer.resize(bounds.width, bounds.height); - } - - // leverage the anchor to account for the offset of the element - this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); - this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); - - // this._cachedSprite.buffer.context.save(); - this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); - - // make sure we set the alpha of the graphics to 1 for the render.. - this.worldAlpha = 1; - - // now render the graphic.. - CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); - - this._cachedSprite.alpha = this.alpha; -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Graphics.prototype.updateCachedSpriteTexture = function () -{ - var cachedSprite = this._cachedSprite; - var texture = cachedSprite.texture; - var canvas = cachedSprite.buffer.canvas; - - texture.baseTexture.width = canvas.width; - texture.baseTexture.height = canvas.height; - texture.crop.width = texture.frame.width = canvas.width; - texture.crop.height = texture.frame.height = canvas.height; - - cachedSprite._width = canvas.width; - cachedSprite._height = canvas.height; - - // update the dirty base textures - texture.baseTexture.dirty(); -}; - -/** - * Destroys a previous cached sprite. - * - */ -Graphics.prototype.destroyCachedSprite = function () -{ - this._cachedSprite.texture.destroy(true); - - // let the gc collect the unused sprite - // TODO could be object pooled! - this._cachedSprite = null; -}; - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. - * @return {GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = this.filling; - this.currentPath = data; - } - - this.dirty = true; - - return data; -}; diff --git a/src/core/primitives/GraphicsData.js b/src/core/primitives/GraphicsData.js deleted file mode 100644 index 788f6f2..0000000 --- a/src/core/primitives/GraphicsData.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * A GraphicsData object. - * - * @class - * @namespace PIXI - */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - this.lineWidth = lineWidth; - this.lineColor = lineColor; - this.lineAlpha = lineAlpha; - this._lineTint = lineColor; - - this.fillColor = fillColor; - this.fillAlpha = fillAlpha; - this._fillTint = fillColor; - this.fill = fill; - - this.shape = shape; - this.type = shape.type; -} - -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {GraphicsData} - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js deleted file mode 100644 index 1a508f9..0000000 --- a/src/core/primitives/GraphicsRenderer.js +++ /dev/null @@ -1,874 +0,0 @@ -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; - -/** - * 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 ); - -// var matrix = graphics.worldTransform.clone(); -// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); -// matrix.append(graphics.worldTransform); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.maskManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.maskManager.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.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); - - 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/primitives/WebGLGraphicsData.js b/src/core/primitives/WebGLGraphicsData.js deleted file mode 100755 index 41be102..0000000 --- a/src/core/primitives/WebGLGraphicsData.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @class - * @private - */ -function WebGLGraphicsData(gl) { - this.gl = gl; - - //TODO does this need to be split before uploding?? - this.color = [0,0,0]; // color split! - this.points = []; - this.indices = []; - this.buffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - this.mode = 1; - this.alpha = 1; - this.dirty = true; -} - -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; -module.exports = WebGLGraphicsData; - -/** - * - */ -WebGLGraphicsData.prototype.reset = function () { - this.points = []; - this.indices = []; -}; - -/** - * - */ -WebGLGraphicsData.prototype.upload = function () { - var gl = this.gl; - -// this.lastIndex = graphics.graphicsData.length; - this.glPoints = new Float32Array(this.points); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); - - this.glIndicies = new Uint16Array(this.indices); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); - - this.dirty = false; -}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6c1fb7d..4e82baa 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -174,6 +174,9 @@ // time to create the render managers! each one focuses on managing a state in webGL + // initialize the context so it is ready for the managers. + this._initContext(); + /** * Deals with managing the shader programs and their attribs * @member {WebGLShaderManager} @@ -204,17 +207,11 @@ */ this.blendModeManager = new WebGLBlendModeManager(this); - - this.blendModes = null; this._boundUpdateTexture = this.updateTexture.bind(this); this._boundDestroyTexture = this.destroyTexture.bind(this); - - - // time init the context.. - this._initContext(); this.currentRenderTarget = this.renderTarget; @@ -228,27 +225,18 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - /** - * Manages the renderer of specific objects. - * - * @member {object} - */ - this.objectRenderers = {}; - this.currentRenderer = new ObjectRenderer(); - - // create an instance of each registered object renderer - for (var o in WebGLRenderer._objectRenderers) { - this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); - } - + this.initPlugins(); } // constructor WebGLRenderer.prototype.constructor = WebGLRenderer; module.exports = WebGLRenderer; +WebGLRenderer.glContextId = 0; + +utils.pluginTarget.mixin(WebGLRenderer); utils.eventTarget.mixin(WebGLRenderer.prototype); Object.defineProperties(WebGLRenderer.prototype, { @@ -297,11 +285,9 @@ gl.enable(gl.BLEND); this.renderTarget = new RenderTarget(this.gl, this.width, this.height, null, true); - this.emit('context', gl); - // now resize and we are good to go! this.resize(this.width, this.height); }; @@ -349,7 +335,7 @@ gl.clear(gl.COLOR_BUFFER_BIT); } - this.renderDisplayObject(object, this.renderTarget)//this.projection); + this.renderDisplayObject(object, this.renderTarget);//this.projection); }; /** @@ -536,12 +522,7 @@ this.maskManager.destroy(); this.filterManager.destroy(); - for (var o in this.objectRenderers) { - this.objectRenderers[o].destroy(); - this.objectRenderers[o] = null; - } - - this.objectRenderers = null; + this.destroyPlugins(); // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -611,11 +592,3 @@ this.blendModes[CONST.blendModes.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; } }; - -WebGLRenderer.glContextId = 0; -WebGLRenderer._objectRenderers = {}; - -WebGLRenderer.registerObjectRenderer = function (name, ctor) { - WebGLRenderer._objectRenderers[name] = ctor; -}; - diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js index 87c99f0..d6ed3c6 100644 --- a/src/core/renderers/webgl/managers/WebGLFilterManager.js +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -51,7 +51,7 @@ */ WebGLFilterManager.prototype.begin = function (buffer) { - this.defaultShader = this.renderer.shaderManager.defaultShader; + this.defaultShader = this.renderer.shaderManager.plugins.defaultShader; this.width = this.renderer.projection.x * 2; this.height = -this.renderer.projection.y * 2; @@ -385,7 +385,7 @@ if (!shader) { - shader = new Shader(gl); + shader = new Shader(this.renderer.shaderManager); shader.fragmentSrc = filter.fragmentSrc; shader.uniforms = filter.uniforms; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js index 6a1f782..d6a11c1 100644 --- a/src/core/renderers/webgl/managers/WebGLMaskManager.js +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -126,11 +126,11 @@ // bind the graphics object.. var projection = this.renderer.projection, offset = this.renderer.offset, - shader;// = this.renderer.shaderManager.primitiveShader; + shader;// = this.renderer.shaderManager.plugins.primitiveShader; if (webGLData.mode === 1) { - shader = this.renderer.shaderManager.complexPrimitiveShader; + shader = this.renderer.shaderManager.plugins.complexPrimitiveShader; this.renderer.shaderManager.setShader(shader); @@ -156,7 +156,7 @@ else { //this.renderer.shaderManager.activatePrimitiveShader(); - shader = this.renderer.shaderManager.primitiveShader; + shader = this.renderer.shaderManager.plugins.primitiveShader; this.renderer.shaderManager.setShader( shader ); gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); @@ -292,11 +292,11 @@ */ WebGLMaskManager.prototype.pushMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); if (maskData.dirty) { - this.renderer.objectRenderers.graphics.updateGraphics(maskData, this.renderer.gl); + this.renderer.plugins.graphics.updateGraphics(maskData, this.renderer.gl); } if (!maskData._webGL[this.renderer.gl.id].data.length) @@ -314,7 +314,7 @@ */ WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); this.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); }; diff --git a/src/core/renderers/webgl/managers/WebGLShaderManager.js b/src/core/renderers/webgl/managers/WebGLShaderManager.js index 90c6477..9ebb58e 100644 --- a/src/core/renderers/webgl/managers/WebGLShaderManager.js +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -1,9 +1,5 @@ var WebGLManager = require('./WebGLManager'), - PrimitiveShader = require('../shaders/PrimitiveShader'), - ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), - Shader = require('../shaders/Shader'), - FastShader = require('../shaders/FastShader'), - StripShader = require('../shaders/StripShader'); + utils = require('../../../utils'); /** * @class @@ -51,41 +47,16 @@ */ this.currentShader = null; - // this shader is used for rendering primitives - this.primitiveShader = null; - - // this shader is used for rendering triangle strips - this.complexPrimitiveShader = null; - - // this shader is used for the default sprite rendering - this.defaultShader = null; - - // this shader is used for the fast sprite rendering - this.fastShader = null; - - // the next one is used for rendering triangle strips - this.stripShader = null; + this.initPlugins(); // listen for context and update necessary shaders var self = this; - this.renderer.on('context', function (event) + this.renderer.on('context', function () { - var gl = event.data; - - // this shader is used for rendering primitives - self.primitiveShader = new PrimitiveShader(gl); - - // this shader is used for rendering triangle strips - self.complexPrimitiveShader = new ComplexPrimitiveShader(gl); - - // this shader is used for the default sprite rendering - self.defaultShader = new Shader(gl); - - // this shader is used for the fast sprite rendering - self.fastShader = new FastShader(gl); - - // the next one is used for rendering triangle strips - self.stripShader = new StripShader(gl); + for (var o in this.plugins) + { + this.plugins[o] = new (this.plugins[o].constructor)(self); + } self.setShader(self.defaultShader); }); @@ -95,6 +66,8 @@ WebGLShaderManager.prototype.constructor = WebGLShaderManager; module.exports = WebGLShaderManager; +utils.pluginTarget.mixin(WebGLShaderManager); + /** * Takes the attributes given in parameters. * @@ -164,24 +137,11 @@ */ WebGLShaderManager.prototype.destroy = function () { + this.destroyPlugins(); + this.attribState = null; this.tempAttribState = null; - this.primitiveShader.destroy(); - this.primitiveShader = null; - - this.complexPrimitiveShader.destroy(); - this.complexPrimitiveShader = null; - - this.defaultShader.destroy(); - this.defaultShader = null; - - this.fastShader.destroy(); - this.fastShader = null; - - this.stripShader.destroy(); - this.stripShader = null; - this.renderer = null; }; diff --git a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js b/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js deleted file mode 100644 index f8a07da..0000000 --- a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js +++ /dev/null @@ -1,53 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function ComplexPrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform vec3 tint;', - 'uniform float alpha;', - 'uniform vec3 color;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - color: { type: '3f', value: [0,0,0] }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); -ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; -module.exports = ComplexPrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/FastShader.js b/src/core/renderers/webgl/shaders/FastShader.js index d198f10..c80d040 100644 --- a/src/core/renderers/webgl/shaders/FastShader.js +++ b/src/core/renderers/webgl/shaders/FastShader.js @@ -1,15 +1,16 @@ -var Shader = require('./Shader'); +var Shader = require('./Shader'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @extends Shader * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. */ -function FastShader(gl) +function FastShader(shaderManager) { Shader.call(this, - gl, + shaderManager, // vertex shader [ 'attribute vec2 aVertexPosition;', @@ -70,3 +71,5 @@ FastShader.prototype = Object.create(Shader.prototype); FastShader.prototype.constructor = FastShader; module.exports = FastShader; + +WebGLShaderManager.registerPlugin('fastShader', FastShader); diff --git a/src/core/renderers/webgl/shaders/PrimitiveShader.js b/src/core/renderers/webgl/shaders/PrimitiveShader.js deleted file mode 100644 index 753c475..0000000 --- a/src/core/renderers/webgl/shaders/PrimitiveShader.js +++ /dev/null @@ -1,55 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform float alpha;', - 'uniform float flipY;', - 'uniform vec3 tint;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aColor:0 - } - ); -} - -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; -module.exports = PrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/Shader.js b/src/core/renderers/webgl/shaders/Shader.js index d065a76..c60c5f9 100644 --- a/src/core/renderers/webgl/shaders/Shader.js +++ b/src/core/renderers/webgl/shaders/Shader.js @@ -1,12 +1,16 @@ -var utils = require('../../../utils'); +var utils = require('../../../utils'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @namespace PIXI - * @param [fragmentSrc] {string} The source of the fragment shader. + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. * @param [vertexSrc] {string} The source of the vertex shader. + * @param [fragmentSrc] {string} The source of the fragment shader. + * @param customUniforms {object} Custom uniforms to use to augment the built-in ones. + * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Shader(gl, vertexSrc, fragmentSrc, customUniforms, customAttributes) +function Shader(shaderManager, vertexSrc, fragmentSrc, customUniforms, customAttributes) { /** * @member {number} @@ -18,7 +22,7 @@ * @member {WebGLContext} * @readonly */ - this.gl = gl; + this.gl = shaderManager.renderer.gl; /** * The WebGL program. @@ -31,8 +35,8 @@ uSampler: { type: 'sampler2D', value: 0 }, projectionVector: { type: '2f', value: { x: 0, y: 0 } }, offsetVector: { type: '2f', value: { x: 0, y: 0 } }, - projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, - 0, 1, 0, + projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, + 0, 1, 0, 0, 0, 1) }, dimensions: { type: '4f', value: new Float32Array(4) } }; @@ -103,6 +107,8 @@ Shader.prototype.constructor = Shader; module.exports = Shader; +WebGLShaderManager.registerPlugin('defaultShader', Shader); + Shader.prototype.init = function () { this.compile(); @@ -506,4 +512,4 @@ } return shader; -}; \ No newline at end of file +}; diff --git a/src/core/renderers/webgl/shaders/SpriteShader.js b/src/core/renderers/webgl/shaders/SpriteShader.js deleted file mode 100644 index 1805b0a..0000000 --- a/src/core/renderers/webgl/shaders/SpriteShader.js +++ /dev/null @@ -1,42 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function SpriteShader(gl) -{ - Shader.call(this, - gl, - null, - // fragment shader - [ - 'precision lowp float;', - - 'varying vec2 vTextureCoord;', - 'varying vec4 vColor;', - - 'uniform sampler2D uSampler;', - - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aTextureCoord: 0, - aColor: 0 - } - ); -} - -SpriteShader.prototype = Object.create(Shader.prototype); -SpriteShader.prototype.constructor = SpriteShader; -module.exports = SpriteShader; diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js new file mode 100644 index 0000000..41be102 --- /dev/null +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -0,0 +1,48 @@ +/** + * @class + * @private + */ +function WebGLGraphicsData(gl) { + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +} + +WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; +module.exports = WebGLGraphicsData; + +/** + * + */ +WebGLGraphicsData.prototype.reset = function () { + this.points = []; + this.indices = []; +}; + +/** + * + */ +WebGLGraphicsData.prototype.upload = function () { + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; diff --git a/src/core/index.js b/src/core/index.js index 6f744e9..1d0a398 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -23,12 +23,12 @@ Sprite: require('./sprites/Sprite'), SpriteBatch: require('./sprites/SpriteBatch'), - SpriteRenderer: require('./sprites/SpriteRenderer'), + SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), // primitives - Graphics: require('./primitives/Graphics'), - GraphicsData: require('./primitives/GraphicsData'), - GraphicsRenderer: require('./primitives/GraphicsRenderer'), + Graphics: require('./graphics/Graphics'), + GraphicsData: require('./graphics/GraphicsData'), + GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,10 +42,8 @@ CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), // renderers - webgl - WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), - // WebGLGraphics: require('./renderers/webgl/utils/WebGLGraphics'), - - + WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), + Shader: require('./renderers/webgl/shaders/Shader'), /** * This helper function will automatically detect which renderer you should be using. @@ -103,7 +101,3 @@ return core.autoDetectRenderer(width, height, options, isAndroid); } }; - -// Adding here for now.. sure there is a better place! -core.WebGLRenderer.registerObjectRenderer('sprite', core.SpriteRenderer); -core.WebGLRenderer.registerObjectRenderer('graphics', core.GraphicsRenderer); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index a086950..81de05c 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -268,6 +268,6 @@ matrix.ty = this.ty; return matrix; -} +}; Matrix.IDENTITY = new Matrix(); diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js deleted file mode 100644 index 8db9b45..0000000 --- a/src/core/primitives/Graphics.js +++ /dev/null @@ -1,1116 +0,0 @@ -var DisplayObjectContainer = require('../display/DisplayObjectContainer'), - //Sprite = require('../display/Sprite'), - Texture = require('../textures/Texture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), - // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), - GraphicsData = require('./GraphicsData'), - math = require('../math'), - CONST = require('../const'); - -/** - * The Graphics class contains methods used to draw primitive shapes such as lines, circles and - * rectangles to the display, and color and fill them. - * - * @class - * @extends DisplayObjectContainer - * @namespace PIXI - */ -function Graphics() -{ - DisplayObjectContainer.call(this); - - this.renderable = true; - - /** - * The alpha value used when filling the Graphics object. - * - * @member {number} - * @default 1 - */ - this.fillAlpha = 1; - - /** - * The width (thickness) of any lines drawn. - * - * @member {number} - * @default 0 - */ - this.lineWidth = 0; - - /** - * The color of any lines drawn. - * - * @member {string} - * @default 0 - */ - this.lineColor = 0; - - /** - * Graphics data - * - * @member {GraphicsData[]} - * @private - */ - this.graphicsData = []; - - /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * Current path - * - * @member {GraphicsData} - * @private - */ - this.currentPath = null; - - /** - * Array containing some WebGL-related properties used by the WebGL renderer. - * - * @member {object} - * @private - */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; - - /** - * Whether this shape is being used as a mask. - * - * @member {boolean} - */ - this.isMask = false; - - /** - * The bounds' padding used for bounds calculation. - * - * @member {number} - */ - this.boundsPadding = 0; - - /** - * A cache of the local bounds to prevent recalculation. - * - * @member {Rectangle} - * @private - */ - this._localBounds = new math.Rectangle(0,0,1,1); - - /** - * Used to detect if the graphics object has changed. If this is set to true then the graphics - * object will be recalculated. - * - * @member {boolean} - * @private - */ - this.dirty = true; - - /** - * Used to detect if the WebGL graphics object has changed. If this is set to true then the - * graphics object will be recalculated. - * - * @member {boolean} - * @private - */ - this.glDirty = false; - - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; -} - -// constructor -Graphics.prototype = Object.create(DisplayObjectContainer.prototype); -Graphics.prototype.constructor = Graphics; -module.exports = Graphics; - -Object.defineProperties(Graphics.prototype, { - /** - * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. - * This is useful if your graphics element does not change often, as it will speed up the rendering - * of the object in exchange for taking up texture memory. It is also useful if you need the graphics - * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if - * you are constantly redrawing the graphics element. - * - * @member {boolean} - * @memberof Graphics# - * @default false - * @private - */ - cacheAsBitmap: { - get: function () - { - return this._cacheAsBitmap; - }, - set: function (value) - { - this._cacheAsBitmap = value; - - if (this._cacheAsBitmap) - { - this._generateCachedSprite(); - } - else - { - this.destroyCachedSprite(); - this.dirty = true; - } - } - } -}); - -/** - * Creates a new Graphics object with the same values as this one. - * - * @return {Graphics} - */ -GraphicsData.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = this.dirty; - clone.glDirty = this.glDirty; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData.clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {Graphics} - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (arguments.length < 3) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); - } - else - { - // otherwise its empty so lets just set the line properties - this.currentPath.lineWidth = this.lineWidth; - this.currentPath.lineColor = this.lineColor; - this.currentPath.lineAlpha = this.lineAlpha; - } - } - - return this; -}; - -/** - * Moves the current drawing position to x, y. - * - * @param x {number} the X coordinate to move to - * @param y {number} the Y coordinate to move to - * @return {Graphics} - */ -Graphics.prototype.moveTo = function (x, y) -{ - this.drawShape(new math.Polygon([x,y])); - - return this; -}; - -/** - * Draws a line using the current line style from the current drawing position to (x, y); - * The current drawing position is then set to (x, y). - * - * @param x {number} the X coordinate to draw to - * @param y {number} the Y coordinate to draw to - * @return {Graphics} - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a quadratic bezier curve and then draws it. - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @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 {Graphics} - */ -Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var xa, - ya, - n = 20, - points = this.currentPath.shape.points; - - if (points.length === 0) - { - this.moveTo(0, 0); - } - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - for (var i = 1; i <= n; ++i) - { - j = i / n; - - xa = fromX + ( (cpX - fromX) * j ); - ya = fromY + ( (cpY - fromY) * j ); - - points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), - ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); - } - - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a bezier curve and then draws it. - * - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param cpX2 {number} Second Control point x - * @param cpY2 {number} Second Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {Graphics} - */ -Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var n = 20, - dt, - dt2, - dt3, - t2, - t3, - points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - - for (var i = 1; i <= n; ++i) - { - j = i / n; - - dt = (1 - j); - dt2 = dt * dt; - dt3 = dt2 * dt; - - t2 = j * j; - t3 = t2 * j; - - points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, - dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); - } - - this.dirty = true; - - return this; -}; - -/** - * The arcTo() method creates an arc/curve between two tangents on the canvas. - * - * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! - * - * @param x1 {number} The x-coordinate of the beginning of the arc - * @param y1 {number} The y-coordinate of the beginning of the arc - * @param x2 {number} The x-coordinate of the end of the arc - * @param y2 {number} The y-coordinate of the end of the arc - * @param radius {number} The radius of the arc - * @return {Graphics} - */ -Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points.push(x1, y1); - } - } - else - { - this.moveTo(x1, y1); - } - - var points = this.currentPath.shape.points, - fromX = points[points.length-2], - fromY = points[points.length-1], - a1 = fromY - y1, - b1 = fromX - x1, - a2 = y2 - y1, - b2 = x2 - x1, - mm = Math.abs(a1 * b2 - b1 * a2); - - if (mm < 1.0e-8 || radius === 0) - { - if (points[points.length-2] !== x1 || points[points.length-1] !== y1) - { - points.push(x1, y1); - } - } - else - { - var dd = a1 * a1 + b1 * b1, - cc = a2 * a2 + b2 * b2, - tt = a1 * a2 + b1 * b2, - k1 = radius * Math.sqrt(dd) / mm, - k2 = radius * Math.sqrt(cc) / mm, - j1 = k1 * tt / dd, - j2 = k2 * tt / cc, - cx = k1 * b2 + k2 * b1, - cy = k1 * a2 + k2 * a1, - px = b1 * (k2 + j1), - py = a1 * (k2 + j1), - qx = b2 * (k1 + j2), - qy = a2 * (k1 + j2), - startAngle = Math.atan2(py - cy, px - cx), - endAngle = Math.atan2(qy - cy, qx - cx); - - this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); - } - - this.dirty = true; - - return this; -}; - -/** - * The arc method creates an arc/curve (used to create circles, or parts of circles). - * - * @param cx {number} The x-coordinate of the center of the circle - * @param cy {number} The y-coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) - * @param endAngle {number} The ending angle, in radians - * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {Graphics} - */ -Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - var points; - - // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... - if (this.currentPath) - { - points = this.currentPath.shape.points; - - if (points.length === 0) - { - points.push(startX, startY); - } - else if (points[points.length-2] !== startX || points[points.length-1] !== startY) - { - points.push(startX, startY); - } - } - else - { - this.moveTo(startX, startY); - points = this.currentPath.shape.points; - } - - if (startAngle === endAngle) - { - return this; - } - - if (!anticlockwise && endAngle <= startAngle) - { - endAngle += Math.PI * 2; - } - else if (anticlockwise && startAngle <= endAngle) - { - startAngle += Math.PI * 2; - } - - var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); - var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; - - if (sweep === 0) - { - return this; - } - - var theta = sweep/(segs*2); - var theta2 = theta*2; - - var cTheta = Math.cos(theta); - var sTheta = Math.sin(theta); - - var segMinus = segs - 1; - - var remainder = ( segMinus % 1 ) / segMinus; - - for (var i = 0; i <= segMinus; ++i) - { - var real = i + remainder * i; - var angle = ((theta) + startAngle + (theta2 * real)); - - var c = Math.cos(angle); - var s = -Math.sin(angle); - - points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, - ( (cTheta * -s) + (sTheta * c) ) * radius + cy); - } - - this.dirty = true; - - return this; -}; - -/** - * Specifies a simple one-color fill that subsequent calls to other Graphics methods - * (such as lineTo() or drawCircle()) use when drawing. - * - * @param color {number} the color of the fill - * @param alpha {number} the alpha of the fill - * @return {Graphics} - */ -Graphics.prototype.beginFill = function (color, alpha) -{ - this.filling = true; - this.fillColor = color || 0; - this.fillAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length <= 2) - { - this.currentPath.fill = this.filling; - this.currentPath.fillColor = this.fillColor; - this.currentPath.fillAlpha = this.fillAlpha; - } - } - return this; -}; - -/** - * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. - * - * @return {Graphics} - */ -Graphics.prototype.endFill = function () -{ - this.filling = false; - this.fillColor = null; - this.fillAlpha = 1; - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @return {Graphics} - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {Graphics} - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {Graphics} - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {Array} The path data used to construct the polygon. - * @return {Graphics} - */ -Graphics.prototype.drawPolygon = function (path) -{ - if (!(path instanceof Array)) - { - path = Array.prototype.slice.call(arguments); - } - - this.drawShape(new math.Polygon(path)); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {Graphics} - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty = true; - this.clearDirty = true; - this.graphicsData = []; - - return this; -}; - -/** - * Useful function that returns a texture of the graphics object that can then be used to create sprites - * This can be quite useful if your geometry is complicated and needs to be reused multiple times. - * - * @param resolution {number} The resolution of the texture being generated - * @param scaleMode {number} Should be one of the scaleMode consts - * @return {Texture} a texture of the graphics object - */ -Graphics.prototype.generateTexture = function (resolution, scaleMode) -{ - resolution = resolution || 1; - - var bounds = this.getBounds(); - - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); - - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - canvasBuffer.context.scale(resolution, resolution); - - canvasBuffer.context.translate(-bounds.x,-bounds.y); - - CanvasGraphics.renderGraphics(this, canvasBuffer.context); - - return texture; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (this.isMask === true) - { - return; - } - - // this code may still be needed so leaving for now.. - // - /* - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture on the gpu too! - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.worldAlpha = this.worldAlpha; - - Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); - - return; - } - - */ - - if (this.glDirty) - { - this.dirty = true; - this.glDirty = false; - } - - renderer.setObjectRenderer(renderer.objectRenderers.graphics); - renderer.objectRenderers.graphics.render(this); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} - * @private - */ -Graphics.prototype.renderCanvas = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.alpha <= 0 || this.isMask === true) - { - return; - } - - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.alpha = this.alpha; - - Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); - - return; - } - else - { - var context = renderer.context; - var transform = this.worldTransform; - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - var resolution = renderer.resolution; - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } - } -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @return {Rectangle} the rectangular bounding area - */ -Graphics.prototype.getBounds = function (matrix) -{ - // return an empty object if the item is a mask! - if (this.isMask) - { - return math.Rectangle.EMPTY; - } - - if (this.dirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.dirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - return this._bounds; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; - - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; -}; - -/** - * Generates the cached sprite when the sprite has cacheAsBitmap = true - * - * @private - */ -Graphics.prototype._generateCachedSprite = function () -{ - var bounds = this.getLocalBounds(); - - if (!this._cachedSprite) - { - var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); - var texture = Texture.fromCanvas(canvasBuffer.canvas); - - this._cachedSprite = new Sprite(texture); - this._cachedSprite.buffer = canvasBuffer; - - this._cachedSprite.worldTransform = this.worldTransform; - } - else - { - this._cachedSprite.buffer.resize(bounds.width, bounds.height); - } - - // leverage the anchor to account for the offset of the element - this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); - this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); - - // this._cachedSprite.buffer.context.save(); - this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); - - // make sure we set the alpha of the graphics to 1 for the render.. - this.worldAlpha = 1; - - // now render the graphic.. - CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); - - this._cachedSprite.alpha = this.alpha; -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Graphics.prototype.updateCachedSpriteTexture = function () -{ - var cachedSprite = this._cachedSprite; - var texture = cachedSprite.texture; - var canvas = cachedSprite.buffer.canvas; - - texture.baseTexture.width = canvas.width; - texture.baseTexture.height = canvas.height; - texture.crop.width = texture.frame.width = canvas.width; - texture.crop.height = texture.frame.height = canvas.height; - - cachedSprite._width = canvas.width; - cachedSprite._height = canvas.height; - - // update the dirty base textures - texture.baseTexture.dirty(); -}; - -/** - * Destroys a previous cached sprite. - * - */ -Graphics.prototype.destroyCachedSprite = function () -{ - this._cachedSprite.texture.destroy(true); - - // let the gc collect the unused sprite - // TODO could be object pooled! - this._cachedSprite = null; -}; - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. - * @return {GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = this.filling; - this.currentPath = data; - } - - this.dirty = true; - - return data; -}; diff --git a/src/core/primitives/GraphicsData.js b/src/core/primitives/GraphicsData.js deleted file mode 100644 index 788f6f2..0000000 --- a/src/core/primitives/GraphicsData.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * A GraphicsData object. - * - * @class - * @namespace PIXI - */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - this.lineWidth = lineWidth; - this.lineColor = lineColor; - this.lineAlpha = lineAlpha; - this._lineTint = lineColor; - - this.fillColor = fillColor; - this.fillAlpha = fillAlpha; - this._fillTint = fillColor; - this.fill = fill; - - this.shape = shape; - this.type = shape.type; -} - -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {GraphicsData} - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js deleted file mode 100644 index 1a508f9..0000000 --- a/src/core/primitives/GraphicsRenderer.js +++ /dev/null @@ -1,874 +0,0 @@ -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; - -/** - * 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 ); - -// var matrix = graphics.worldTransform.clone(); -// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); -// matrix.append(graphics.worldTransform); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.maskManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.maskManager.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.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); - - 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/primitives/WebGLGraphicsData.js b/src/core/primitives/WebGLGraphicsData.js deleted file mode 100755 index 41be102..0000000 --- a/src/core/primitives/WebGLGraphicsData.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @class - * @private - */ -function WebGLGraphicsData(gl) { - this.gl = gl; - - //TODO does this need to be split before uploding?? - this.color = [0,0,0]; // color split! - this.points = []; - this.indices = []; - this.buffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - this.mode = 1; - this.alpha = 1; - this.dirty = true; -} - -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; -module.exports = WebGLGraphicsData; - -/** - * - */ -WebGLGraphicsData.prototype.reset = function () { - this.points = []; - this.indices = []; -}; - -/** - * - */ -WebGLGraphicsData.prototype.upload = function () { - var gl = this.gl; - -// this.lastIndex = graphics.graphicsData.length; - this.glPoints = new Float32Array(this.points); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); - - this.glIndicies = new Uint16Array(this.indices); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); - - this.dirty = false; -}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6c1fb7d..4e82baa 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -174,6 +174,9 @@ // time to create the render managers! each one focuses on managing a state in webGL + // initialize the context so it is ready for the managers. + this._initContext(); + /** * Deals with managing the shader programs and their attribs * @member {WebGLShaderManager} @@ -204,17 +207,11 @@ */ this.blendModeManager = new WebGLBlendModeManager(this); - - this.blendModes = null; this._boundUpdateTexture = this.updateTexture.bind(this); this._boundDestroyTexture = this.destroyTexture.bind(this); - - - // time init the context.. - this._initContext(); this.currentRenderTarget = this.renderTarget; @@ -228,27 +225,18 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - /** - * Manages the renderer of specific objects. - * - * @member {object} - */ - this.objectRenderers = {}; - this.currentRenderer = new ObjectRenderer(); - - // create an instance of each registered object renderer - for (var o in WebGLRenderer._objectRenderers) { - this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); - } - + this.initPlugins(); } // constructor WebGLRenderer.prototype.constructor = WebGLRenderer; module.exports = WebGLRenderer; +WebGLRenderer.glContextId = 0; + +utils.pluginTarget.mixin(WebGLRenderer); utils.eventTarget.mixin(WebGLRenderer.prototype); Object.defineProperties(WebGLRenderer.prototype, { @@ -297,11 +285,9 @@ gl.enable(gl.BLEND); this.renderTarget = new RenderTarget(this.gl, this.width, this.height, null, true); - this.emit('context', gl); - // now resize and we are good to go! this.resize(this.width, this.height); }; @@ -349,7 +335,7 @@ gl.clear(gl.COLOR_BUFFER_BIT); } - this.renderDisplayObject(object, this.renderTarget)//this.projection); + this.renderDisplayObject(object, this.renderTarget);//this.projection); }; /** @@ -536,12 +522,7 @@ this.maskManager.destroy(); this.filterManager.destroy(); - for (var o in this.objectRenderers) { - this.objectRenderers[o].destroy(); - this.objectRenderers[o] = null; - } - - this.objectRenderers = null; + this.destroyPlugins(); // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -611,11 +592,3 @@ this.blendModes[CONST.blendModes.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; } }; - -WebGLRenderer.glContextId = 0; -WebGLRenderer._objectRenderers = {}; - -WebGLRenderer.registerObjectRenderer = function (name, ctor) { - WebGLRenderer._objectRenderers[name] = ctor; -}; - diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js index 87c99f0..d6ed3c6 100644 --- a/src/core/renderers/webgl/managers/WebGLFilterManager.js +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -51,7 +51,7 @@ */ WebGLFilterManager.prototype.begin = function (buffer) { - this.defaultShader = this.renderer.shaderManager.defaultShader; + this.defaultShader = this.renderer.shaderManager.plugins.defaultShader; this.width = this.renderer.projection.x * 2; this.height = -this.renderer.projection.y * 2; @@ -385,7 +385,7 @@ if (!shader) { - shader = new Shader(gl); + shader = new Shader(this.renderer.shaderManager); shader.fragmentSrc = filter.fragmentSrc; shader.uniforms = filter.uniforms; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js index 6a1f782..d6a11c1 100644 --- a/src/core/renderers/webgl/managers/WebGLMaskManager.js +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -126,11 +126,11 @@ // bind the graphics object.. var projection = this.renderer.projection, offset = this.renderer.offset, - shader;// = this.renderer.shaderManager.primitiveShader; + shader;// = this.renderer.shaderManager.plugins.primitiveShader; if (webGLData.mode === 1) { - shader = this.renderer.shaderManager.complexPrimitiveShader; + shader = this.renderer.shaderManager.plugins.complexPrimitiveShader; this.renderer.shaderManager.setShader(shader); @@ -156,7 +156,7 @@ else { //this.renderer.shaderManager.activatePrimitiveShader(); - shader = this.renderer.shaderManager.primitiveShader; + shader = this.renderer.shaderManager.plugins.primitiveShader; this.renderer.shaderManager.setShader( shader ); gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); @@ -292,11 +292,11 @@ */ WebGLMaskManager.prototype.pushMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); if (maskData.dirty) { - this.renderer.objectRenderers.graphics.updateGraphics(maskData, this.renderer.gl); + this.renderer.plugins.graphics.updateGraphics(maskData, this.renderer.gl); } if (!maskData._webGL[this.renderer.gl.id].data.length) @@ -314,7 +314,7 @@ */ WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); this.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); }; diff --git a/src/core/renderers/webgl/managers/WebGLShaderManager.js b/src/core/renderers/webgl/managers/WebGLShaderManager.js index 90c6477..9ebb58e 100644 --- a/src/core/renderers/webgl/managers/WebGLShaderManager.js +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -1,9 +1,5 @@ var WebGLManager = require('./WebGLManager'), - PrimitiveShader = require('../shaders/PrimitiveShader'), - ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), - Shader = require('../shaders/Shader'), - FastShader = require('../shaders/FastShader'), - StripShader = require('../shaders/StripShader'); + utils = require('../../../utils'); /** * @class @@ -51,41 +47,16 @@ */ this.currentShader = null; - // this shader is used for rendering primitives - this.primitiveShader = null; - - // this shader is used for rendering triangle strips - this.complexPrimitiveShader = null; - - // this shader is used for the default sprite rendering - this.defaultShader = null; - - // this shader is used for the fast sprite rendering - this.fastShader = null; - - // the next one is used for rendering triangle strips - this.stripShader = null; + this.initPlugins(); // listen for context and update necessary shaders var self = this; - this.renderer.on('context', function (event) + this.renderer.on('context', function () { - var gl = event.data; - - // this shader is used for rendering primitives - self.primitiveShader = new PrimitiveShader(gl); - - // this shader is used for rendering triangle strips - self.complexPrimitiveShader = new ComplexPrimitiveShader(gl); - - // this shader is used for the default sprite rendering - self.defaultShader = new Shader(gl); - - // this shader is used for the fast sprite rendering - self.fastShader = new FastShader(gl); - - // the next one is used for rendering triangle strips - self.stripShader = new StripShader(gl); + for (var o in this.plugins) + { + this.plugins[o] = new (this.plugins[o].constructor)(self); + } self.setShader(self.defaultShader); }); @@ -95,6 +66,8 @@ WebGLShaderManager.prototype.constructor = WebGLShaderManager; module.exports = WebGLShaderManager; +utils.pluginTarget.mixin(WebGLShaderManager); + /** * Takes the attributes given in parameters. * @@ -164,24 +137,11 @@ */ WebGLShaderManager.prototype.destroy = function () { + this.destroyPlugins(); + this.attribState = null; this.tempAttribState = null; - this.primitiveShader.destroy(); - this.primitiveShader = null; - - this.complexPrimitiveShader.destroy(); - this.complexPrimitiveShader = null; - - this.defaultShader.destroy(); - this.defaultShader = null; - - this.fastShader.destroy(); - this.fastShader = null; - - this.stripShader.destroy(); - this.stripShader = null; - this.renderer = null; }; diff --git a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js b/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js deleted file mode 100644 index f8a07da..0000000 --- a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js +++ /dev/null @@ -1,53 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function ComplexPrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform vec3 tint;', - 'uniform float alpha;', - 'uniform vec3 color;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - color: { type: '3f', value: [0,0,0] }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); -ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; -module.exports = ComplexPrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/FastShader.js b/src/core/renderers/webgl/shaders/FastShader.js index d198f10..c80d040 100644 --- a/src/core/renderers/webgl/shaders/FastShader.js +++ b/src/core/renderers/webgl/shaders/FastShader.js @@ -1,15 +1,16 @@ -var Shader = require('./Shader'); +var Shader = require('./Shader'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @extends Shader * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. */ -function FastShader(gl) +function FastShader(shaderManager) { Shader.call(this, - gl, + shaderManager, // vertex shader [ 'attribute vec2 aVertexPosition;', @@ -70,3 +71,5 @@ FastShader.prototype = Object.create(Shader.prototype); FastShader.prototype.constructor = FastShader; module.exports = FastShader; + +WebGLShaderManager.registerPlugin('fastShader', FastShader); diff --git a/src/core/renderers/webgl/shaders/PrimitiveShader.js b/src/core/renderers/webgl/shaders/PrimitiveShader.js deleted file mode 100644 index 753c475..0000000 --- a/src/core/renderers/webgl/shaders/PrimitiveShader.js +++ /dev/null @@ -1,55 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform float alpha;', - 'uniform float flipY;', - 'uniform vec3 tint;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aColor:0 - } - ); -} - -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; -module.exports = PrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/Shader.js b/src/core/renderers/webgl/shaders/Shader.js index d065a76..c60c5f9 100644 --- a/src/core/renderers/webgl/shaders/Shader.js +++ b/src/core/renderers/webgl/shaders/Shader.js @@ -1,12 +1,16 @@ -var utils = require('../../../utils'); +var utils = require('../../../utils'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @namespace PIXI - * @param [fragmentSrc] {string} The source of the fragment shader. + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. * @param [vertexSrc] {string} The source of the vertex shader. + * @param [fragmentSrc] {string} The source of the fragment shader. + * @param customUniforms {object} Custom uniforms to use to augment the built-in ones. + * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Shader(gl, vertexSrc, fragmentSrc, customUniforms, customAttributes) +function Shader(shaderManager, vertexSrc, fragmentSrc, customUniforms, customAttributes) { /** * @member {number} @@ -18,7 +22,7 @@ * @member {WebGLContext} * @readonly */ - this.gl = gl; + this.gl = shaderManager.renderer.gl; /** * The WebGL program. @@ -31,8 +35,8 @@ uSampler: { type: 'sampler2D', value: 0 }, projectionVector: { type: '2f', value: { x: 0, y: 0 } }, offsetVector: { type: '2f', value: { x: 0, y: 0 } }, - projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, - 0, 1, 0, + projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, + 0, 1, 0, 0, 0, 1) }, dimensions: { type: '4f', value: new Float32Array(4) } }; @@ -103,6 +107,8 @@ Shader.prototype.constructor = Shader; module.exports = Shader; +WebGLShaderManager.registerPlugin('defaultShader', Shader); + Shader.prototype.init = function () { this.compile(); @@ -506,4 +512,4 @@ } return shader; -}; \ No newline at end of file +}; diff --git a/src/core/renderers/webgl/shaders/SpriteShader.js b/src/core/renderers/webgl/shaders/SpriteShader.js deleted file mode 100644 index 1805b0a..0000000 --- a/src/core/renderers/webgl/shaders/SpriteShader.js +++ /dev/null @@ -1,42 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function SpriteShader(gl) -{ - Shader.call(this, - gl, - null, - // fragment shader - [ - 'precision lowp float;', - - 'varying vec2 vTextureCoord;', - 'varying vec4 vColor;', - - 'uniform sampler2D uSampler;', - - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aTextureCoord: 0, - aColor: 0 - } - ); -} - -SpriteShader.prototype = Object.create(Shader.prototype); -SpriteShader.prototype.constructor = SpriteShader; -module.exports = SpriteShader; diff --git a/src/core/renderers/webgl/shaders/StripShader.js b/src/core/renderers/webgl/shaders/StripShader.js deleted file mode 100644 index f487381..0000000 --- a/src/core/renderers/webgl/shaders/StripShader.js +++ /dev/null @@ -1,54 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function StripShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - // 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform vec2 projectionVector;', - 'uniform vec2 offsetVector;', - - 'varying vec2 vTextureCoord;', - - 'void main(void){', - ' vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);', - ' v -= offsetVector.xyx;', - ' gl_Position = vec4( v.x / projectionVector.x -1.0, v.y / -projectionVector.y + 1.0 , 0.0, 1.0);', - ' vTextureCoord = aTextureCoord;', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'uniform float alpha;', - 'uniform sampler2D uSampler;', - - 'varying vec2 vTextureCoord;', - - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y)) * alpha;', - '}' - ].join('\n'), - // custom uniforms - { - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -StripShader.prototype = Object.create(Shader.prototype); -StripShader.prototype.constructor = StripShader; -module.exports = StripShader; diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js new file mode 100644 index 0000000..41be102 --- /dev/null +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -0,0 +1,48 @@ +/** + * @class + * @private + */ +function WebGLGraphicsData(gl) { + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +} + +WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; +module.exports = WebGLGraphicsData; + +/** + * + */ +WebGLGraphicsData.prototype.reset = function () { + this.points = []; + this.indices = []; +}; + +/** + * + */ +WebGLGraphicsData.prototype.upload = function () { + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; diff --git a/src/core/index.js b/src/core/index.js index 6f744e9..1d0a398 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -23,12 +23,12 @@ Sprite: require('./sprites/Sprite'), SpriteBatch: require('./sprites/SpriteBatch'), - SpriteRenderer: require('./sprites/SpriteRenderer'), + SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), // primitives - Graphics: require('./primitives/Graphics'), - GraphicsData: require('./primitives/GraphicsData'), - GraphicsRenderer: require('./primitives/GraphicsRenderer'), + Graphics: require('./graphics/Graphics'), + GraphicsData: require('./graphics/GraphicsData'), + GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,10 +42,8 @@ CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), // renderers - webgl - WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), - // WebGLGraphics: require('./renderers/webgl/utils/WebGLGraphics'), - - + WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), + Shader: require('./renderers/webgl/shaders/Shader'), /** * This helper function will automatically detect which renderer you should be using. @@ -103,7 +101,3 @@ return core.autoDetectRenderer(width, height, options, isAndroid); } }; - -// Adding here for now.. sure there is a better place! -core.WebGLRenderer.registerObjectRenderer('sprite', core.SpriteRenderer); -core.WebGLRenderer.registerObjectRenderer('graphics', core.GraphicsRenderer); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index a086950..81de05c 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -268,6 +268,6 @@ matrix.ty = this.ty; return matrix; -} +}; Matrix.IDENTITY = new Matrix(); diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js deleted file mode 100644 index 8db9b45..0000000 --- a/src/core/primitives/Graphics.js +++ /dev/null @@ -1,1116 +0,0 @@ -var DisplayObjectContainer = require('../display/DisplayObjectContainer'), - //Sprite = require('../display/Sprite'), - Texture = require('../textures/Texture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), - // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), - GraphicsData = require('./GraphicsData'), - math = require('../math'), - CONST = require('../const'); - -/** - * The Graphics class contains methods used to draw primitive shapes such as lines, circles and - * rectangles to the display, and color and fill them. - * - * @class - * @extends DisplayObjectContainer - * @namespace PIXI - */ -function Graphics() -{ - DisplayObjectContainer.call(this); - - this.renderable = true; - - /** - * The alpha value used when filling the Graphics object. - * - * @member {number} - * @default 1 - */ - this.fillAlpha = 1; - - /** - * The width (thickness) of any lines drawn. - * - * @member {number} - * @default 0 - */ - this.lineWidth = 0; - - /** - * The color of any lines drawn. - * - * @member {string} - * @default 0 - */ - this.lineColor = 0; - - /** - * Graphics data - * - * @member {GraphicsData[]} - * @private - */ - this.graphicsData = []; - - /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * Current path - * - * @member {GraphicsData} - * @private - */ - this.currentPath = null; - - /** - * Array containing some WebGL-related properties used by the WebGL renderer. - * - * @member {object} - * @private - */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; - - /** - * Whether this shape is being used as a mask. - * - * @member {boolean} - */ - this.isMask = false; - - /** - * The bounds' padding used for bounds calculation. - * - * @member {number} - */ - this.boundsPadding = 0; - - /** - * A cache of the local bounds to prevent recalculation. - * - * @member {Rectangle} - * @private - */ - this._localBounds = new math.Rectangle(0,0,1,1); - - /** - * Used to detect if the graphics object has changed. If this is set to true then the graphics - * object will be recalculated. - * - * @member {boolean} - * @private - */ - this.dirty = true; - - /** - * Used to detect if the WebGL graphics object has changed. If this is set to true then the - * graphics object will be recalculated. - * - * @member {boolean} - * @private - */ - this.glDirty = false; - - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; -} - -// constructor -Graphics.prototype = Object.create(DisplayObjectContainer.prototype); -Graphics.prototype.constructor = Graphics; -module.exports = Graphics; - -Object.defineProperties(Graphics.prototype, { - /** - * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. - * This is useful if your graphics element does not change often, as it will speed up the rendering - * of the object in exchange for taking up texture memory. It is also useful if you need the graphics - * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if - * you are constantly redrawing the graphics element. - * - * @member {boolean} - * @memberof Graphics# - * @default false - * @private - */ - cacheAsBitmap: { - get: function () - { - return this._cacheAsBitmap; - }, - set: function (value) - { - this._cacheAsBitmap = value; - - if (this._cacheAsBitmap) - { - this._generateCachedSprite(); - } - else - { - this.destroyCachedSprite(); - this.dirty = true; - } - } - } -}); - -/** - * Creates a new Graphics object with the same values as this one. - * - * @return {Graphics} - */ -GraphicsData.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = this.dirty; - clone.glDirty = this.glDirty; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData.clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {Graphics} - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (arguments.length < 3) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); - } - else - { - // otherwise its empty so lets just set the line properties - this.currentPath.lineWidth = this.lineWidth; - this.currentPath.lineColor = this.lineColor; - this.currentPath.lineAlpha = this.lineAlpha; - } - } - - return this; -}; - -/** - * Moves the current drawing position to x, y. - * - * @param x {number} the X coordinate to move to - * @param y {number} the Y coordinate to move to - * @return {Graphics} - */ -Graphics.prototype.moveTo = function (x, y) -{ - this.drawShape(new math.Polygon([x,y])); - - return this; -}; - -/** - * Draws a line using the current line style from the current drawing position to (x, y); - * The current drawing position is then set to (x, y). - * - * @param x {number} the X coordinate to draw to - * @param y {number} the Y coordinate to draw to - * @return {Graphics} - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a quadratic bezier curve and then draws it. - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @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 {Graphics} - */ -Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var xa, - ya, - n = 20, - points = this.currentPath.shape.points; - - if (points.length === 0) - { - this.moveTo(0, 0); - } - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - for (var i = 1; i <= n; ++i) - { - j = i / n; - - xa = fromX + ( (cpX - fromX) * j ); - ya = fromY + ( (cpY - fromY) * j ); - - points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), - ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); - } - - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a bezier curve and then draws it. - * - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param cpX2 {number} Second Control point x - * @param cpY2 {number} Second Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {Graphics} - */ -Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var n = 20, - dt, - dt2, - dt3, - t2, - t3, - points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - - for (var i = 1; i <= n; ++i) - { - j = i / n; - - dt = (1 - j); - dt2 = dt * dt; - dt3 = dt2 * dt; - - t2 = j * j; - t3 = t2 * j; - - points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, - dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); - } - - this.dirty = true; - - return this; -}; - -/** - * The arcTo() method creates an arc/curve between two tangents on the canvas. - * - * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! - * - * @param x1 {number} The x-coordinate of the beginning of the arc - * @param y1 {number} The y-coordinate of the beginning of the arc - * @param x2 {number} The x-coordinate of the end of the arc - * @param y2 {number} The y-coordinate of the end of the arc - * @param radius {number} The radius of the arc - * @return {Graphics} - */ -Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points.push(x1, y1); - } - } - else - { - this.moveTo(x1, y1); - } - - var points = this.currentPath.shape.points, - fromX = points[points.length-2], - fromY = points[points.length-1], - a1 = fromY - y1, - b1 = fromX - x1, - a2 = y2 - y1, - b2 = x2 - x1, - mm = Math.abs(a1 * b2 - b1 * a2); - - if (mm < 1.0e-8 || radius === 0) - { - if (points[points.length-2] !== x1 || points[points.length-1] !== y1) - { - points.push(x1, y1); - } - } - else - { - var dd = a1 * a1 + b1 * b1, - cc = a2 * a2 + b2 * b2, - tt = a1 * a2 + b1 * b2, - k1 = radius * Math.sqrt(dd) / mm, - k2 = radius * Math.sqrt(cc) / mm, - j1 = k1 * tt / dd, - j2 = k2 * tt / cc, - cx = k1 * b2 + k2 * b1, - cy = k1 * a2 + k2 * a1, - px = b1 * (k2 + j1), - py = a1 * (k2 + j1), - qx = b2 * (k1 + j2), - qy = a2 * (k1 + j2), - startAngle = Math.atan2(py - cy, px - cx), - endAngle = Math.atan2(qy - cy, qx - cx); - - this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); - } - - this.dirty = true; - - return this; -}; - -/** - * The arc method creates an arc/curve (used to create circles, or parts of circles). - * - * @param cx {number} The x-coordinate of the center of the circle - * @param cy {number} The y-coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) - * @param endAngle {number} The ending angle, in radians - * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {Graphics} - */ -Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - var points; - - // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... - if (this.currentPath) - { - points = this.currentPath.shape.points; - - if (points.length === 0) - { - points.push(startX, startY); - } - else if (points[points.length-2] !== startX || points[points.length-1] !== startY) - { - points.push(startX, startY); - } - } - else - { - this.moveTo(startX, startY); - points = this.currentPath.shape.points; - } - - if (startAngle === endAngle) - { - return this; - } - - if (!anticlockwise && endAngle <= startAngle) - { - endAngle += Math.PI * 2; - } - else if (anticlockwise && startAngle <= endAngle) - { - startAngle += Math.PI * 2; - } - - var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); - var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; - - if (sweep === 0) - { - return this; - } - - var theta = sweep/(segs*2); - var theta2 = theta*2; - - var cTheta = Math.cos(theta); - var sTheta = Math.sin(theta); - - var segMinus = segs - 1; - - var remainder = ( segMinus % 1 ) / segMinus; - - for (var i = 0; i <= segMinus; ++i) - { - var real = i + remainder * i; - var angle = ((theta) + startAngle + (theta2 * real)); - - var c = Math.cos(angle); - var s = -Math.sin(angle); - - points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, - ( (cTheta * -s) + (sTheta * c) ) * radius + cy); - } - - this.dirty = true; - - return this; -}; - -/** - * Specifies a simple one-color fill that subsequent calls to other Graphics methods - * (such as lineTo() or drawCircle()) use when drawing. - * - * @param color {number} the color of the fill - * @param alpha {number} the alpha of the fill - * @return {Graphics} - */ -Graphics.prototype.beginFill = function (color, alpha) -{ - this.filling = true; - this.fillColor = color || 0; - this.fillAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length <= 2) - { - this.currentPath.fill = this.filling; - this.currentPath.fillColor = this.fillColor; - this.currentPath.fillAlpha = this.fillAlpha; - } - } - return this; -}; - -/** - * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. - * - * @return {Graphics} - */ -Graphics.prototype.endFill = function () -{ - this.filling = false; - this.fillColor = null; - this.fillAlpha = 1; - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @return {Graphics} - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {Graphics} - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {Graphics} - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {Array} The path data used to construct the polygon. - * @return {Graphics} - */ -Graphics.prototype.drawPolygon = function (path) -{ - if (!(path instanceof Array)) - { - path = Array.prototype.slice.call(arguments); - } - - this.drawShape(new math.Polygon(path)); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {Graphics} - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty = true; - this.clearDirty = true; - this.graphicsData = []; - - return this; -}; - -/** - * Useful function that returns a texture of the graphics object that can then be used to create sprites - * This can be quite useful if your geometry is complicated and needs to be reused multiple times. - * - * @param resolution {number} The resolution of the texture being generated - * @param scaleMode {number} Should be one of the scaleMode consts - * @return {Texture} a texture of the graphics object - */ -Graphics.prototype.generateTexture = function (resolution, scaleMode) -{ - resolution = resolution || 1; - - var bounds = this.getBounds(); - - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); - - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - canvasBuffer.context.scale(resolution, resolution); - - canvasBuffer.context.translate(-bounds.x,-bounds.y); - - CanvasGraphics.renderGraphics(this, canvasBuffer.context); - - return texture; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (this.isMask === true) - { - return; - } - - // this code may still be needed so leaving for now.. - // - /* - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture on the gpu too! - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.worldAlpha = this.worldAlpha; - - Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); - - return; - } - - */ - - if (this.glDirty) - { - this.dirty = true; - this.glDirty = false; - } - - renderer.setObjectRenderer(renderer.objectRenderers.graphics); - renderer.objectRenderers.graphics.render(this); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} - * @private - */ -Graphics.prototype.renderCanvas = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.alpha <= 0 || this.isMask === true) - { - return; - } - - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.alpha = this.alpha; - - Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); - - return; - } - else - { - var context = renderer.context; - var transform = this.worldTransform; - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - var resolution = renderer.resolution; - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } - } -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @return {Rectangle} the rectangular bounding area - */ -Graphics.prototype.getBounds = function (matrix) -{ - // return an empty object if the item is a mask! - if (this.isMask) - { - return math.Rectangle.EMPTY; - } - - if (this.dirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.dirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - return this._bounds; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; - - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; -}; - -/** - * Generates the cached sprite when the sprite has cacheAsBitmap = true - * - * @private - */ -Graphics.prototype._generateCachedSprite = function () -{ - var bounds = this.getLocalBounds(); - - if (!this._cachedSprite) - { - var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); - var texture = Texture.fromCanvas(canvasBuffer.canvas); - - this._cachedSprite = new Sprite(texture); - this._cachedSprite.buffer = canvasBuffer; - - this._cachedSprite.worldTransform = this.worldTransform; - } - else - { - this._cachedSprite.buffer.resize(bounds.width, bounds.height); - } - - // leverage the anchor to account for the offset of the element - this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); - this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); - - // this._cachedSprite.buffer.context.save(); - this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); - - // make sure we set the alpha of the graphics to 1 for the render.. - this.worldAlpha = 1; - - // now render the graphic.. - CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); - - this._cachedSprite.alpha = this.alpha; -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Graphics.prototype.updateCachedSpriteTexture = function () -{ - var cachedSprite = this._cachedSprite; - var texture = cachedSprite.texture; - var canvas = cachedSprite.buffer.canvas; - - texture.baseTexture.width = canvas.width; - texture.baseTexture.height = canvas.height; - texture.crop.width = texture.frame.width = canvas.width; - texture.crop.height = texture.frame.height = canvas.height; - - cachedSprite._width = canvas.width; - cachedSprite._height = canvas.height; - - // update the dirty base textures - texture.baseTexture.dirty(); -}; - -/** - * Destroys a previous cached sprite. - * - */ -Graphics.prototype.destroyCachedSprite = function () -{ - this._cachedSprite.texture.destroy(true); - - // let the gc collect the unused sprite - // TODO could be object pooled! - this._cachedSprite = null; -}; - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. - * @return {GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = this.filling; - this.currentPath = data; - } - - this.dirty = true; - - return data; -}; diff --git a/src/core/primitives/GraphicsData.js b/src/core/primitives/GraphicsData.js deleted file mode 100644 index 788f6f2..0000000 --- a/src/core/primitives/GraphicsData.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * A GraphicsData object. - * - * @class - * @namespace PIXI - */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - this.lineWidth = lineWidth; - this.lineColor = lineColor; - this.lineAlpha = lineAlpha; - this._lineTint = lineColor; - - this.fillColor = fillColor; - this.fillAlpha = fillAlpha; - this._fillTint = fillColor; - this.fill = fill; - - this.shape = shape; - this.type = shape.type; -} - -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {GraphicsData} - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js deleted file mode 100644 index 1a508f9..0000000 --- a/src/core/primitives/GraphicsRenderer.js +++ /dev/null @@ -1,874 +0,0 @@ -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; - -/** - * 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 ); - -// var matrix = graphics.worldTransform.clone(); -// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); -// matrix.append(graphics.worldTransform); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.maskManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.maskManager.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.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); - - 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/primitives/WebGLGraphicsData.js b/src/core/primitives/WebGLGraphicsData.js deleted file mode 100755 index 41be102..0000000 --- a/src/core/primitives/WebGLGraphicsData.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @class - * @private - */ -function WebGLGraphicsData(gl) { - this.gl = gl; - - //TODO does this need to be split before uploding?? - this.color = [0,0,0]; // color split! - this.points = []; - this.indices = []; - this.buffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - this.mode = 1; - this.alpha = 1; - this.dirty = true; -} - -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; -module.exports = WebGLGraphicsData; - -/** - * - */ -WebGLGraphicsData.prototype.reset = function () { - this.points = []; - this.indices = []; -}; - -/** - * - */ -WebGLGraphicsData.prototype.upload = function () { - var gl = this.gl; - -// this.lastIndex = graphics.graphicsData.length; - this.glPoints = new Float32Array(this.points); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); - - this.glIndicies = new Uint16Array(this.indices); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); - - this.dirty = false; -}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6c1fb7d..4e82baa 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -174,6 +174,9 @@ // time to create the render managers! each one focuses on managing a state in webGL + // initialize the context so it is ready for the managers. + this._initContext(); + /** * Deals with managing the shader programs and their attribs * @member {WebGLShaderManager} @@ -204,17 +207,11 @@ */ this.blendModeManager = new WebGLBlendModeManager(this); - - this.blendModes = null; this._boundUpdateTexture = this.updateTexture.bind(this); this._boundDestroyTexture = this.destroyTexture.bind(this); - - - // time init the context.. - this._initContext(); this.currentRenderTarget = this.renderTarget; @@ -228,27 +225,18 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - /** - * Manages the renderer of specific objects. - * - * @member {object} - */ - this.objectRenderers = {}; - this.currentRenderer = new ObjectRenderer(); - - // create an instance of each registered object renderer - for (var o in WebGLRenderer._objectRenderers) { - this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); - } - + this.initPlugins(); } // constructor WebGLRenderer.prototype.constructor = WebGLRenderer; module.exports = WebGLRenderer; +WebGLRenderer.glContextId = 0; + +utils.pluginTarget.mixin(WebGLRenderer); utils.eventTarget.mixin(WebGLRenderer.prototype); Object.defineProperties(WebGLRenderer.prototype, { @@ -297,11 +285,9 @@ gl.enable(gl.BLEND); this.renderTarget = new RenderTarget(this.gl, this.width, this.height, null, true); - this.emit('context', gl); - // now resize and we are good to go! this.resize(this.width, this.height); }; @@ -349,7 +335,7 @@ gl.clear(gl.COLOR_BUFFER_BIT); } - this.renderDisplayObject(object, this.renderTarget)//this.projection); + this.renderDisplayObject(object, this.renderTarget);//this.projection); }; /** @@ -536,12 +522,7 @@ this.maskManager.destroy(); this.filterManager.destroy(); - for (var o in this.objectRenderers) { - this.objectRenderers[o].destroy(); - this.objectRenderers[o] = null; - } - - this.objectRenderers = null; + this.destroyPlugins(); // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -611,11 +592,3 @@ this.blendModes[CONST.blendModes.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; } }; - -WebGLRenderer.glContextId = 0; -WebGLRenderer._objectRenderers = {}; - -WebGLRenderer.registerObjectRenderer = function (name, ctor) { - WebGLRenderer._objectRenderers[name] = ctor; -}; - diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js index 87c99f0..d6ed3c6 100644 --- a/src/core/renderers/webgl/managers/WebGLFilterManager.js +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -51,7 +51,7 @@ */ WebGLFilterManager.prototype.begin = function (buffer) { - this.defaultShader = this.renderer.shaderManager.defaultShader; + this.defaultShader = this.renderer.shaderManager.plugins.defaultShader; this.width = this.renderer.projection.x * 2; this.height = -this.renderer.projection.y * 2; @@ -385,7 +385,7 @@ if (!shader) { - shader = new Shader(gl); + shader = new Shader(this.renderer.shaderManager); shader.fragmentSrc = filter.fragmentSrc; shader.uniforms = filter.uniforms; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js index 6a1f782..d6a11c1 100644 --- a/src/core/renderers/webgl/managers/WebGLMaskManager.js +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -126,11 +126,11 @@ // bind the graphics object.. var projection = this.renderer.projection, offset = this.renderer.offset, - shader;// = this.renderer.shaderManager.primitiveShader; + shader;// = this.renderer.shaderManager.plugins.primitiveShader; if (webGLData.mode === 1) { - shader = this.renderer.shaderManager.complexPrimitiveShader; + shader = this.renderer.shaderManager.plugins.complexPrimitiveShader; this.renderer.shaderManager.setShader(shader); @@ -156,7 +156,7 @@ else { //this.renderer.shaderManager.activatePrimitiveShader(); - shader = this.renderer.shaderManager.primitiveShader; + shader = this.renderer.shaderManager.plugins.primitiveShader; this.renderer.shaderManager.setShader( shader ); gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); @@ -292,11 +292,11 @@ */ WebGLMaskManager.prototype.pushMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); if (maskData.dirty) { - this.renderer.objectRenderers.graphics.updateGraphics(maskData, this.renderer.gl); + this.renderer.plugins.graphics.updateGraphics(maskData, this.renderer.gl); } if (!maskData._webGL[this.renderer.gl.id].data.length) @@ -314,7 +314,7 @@ */ WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); this.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); }; diff --git a/src/core/renderers/webgl/managers/WebGLShaderManager.js b/src/core/renderers/webgl/managers/WebGLShaderManager.js index 90c6477..9ebb58e 100644 --- a/src/core/renderers/webgl/managers/WebGLShaderManager.js +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -1,9 +1,5 @@ var WebGLManager = require('./WebGLManager'), - PrimitiveShader = require('../shaders/PrimitiveShader'), - ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), - Shader = require('../shaders/Shader'), - FastShader = require('../shaders/FastShader'), - StripShader = require('../shaders/StripShader'); + utils = require('../../../utils'); /** * @class @@ -51,41 +47,16 @@ */ this.currentShader = null; - // this shader is used for rendering primitives - this.primitiveShader = null; - - // this shader is used for rendering triangle strips - this.complexPrimitiveShader = null; - - // this shader is used for the default sprite rendering - this.defaultShader = null; - - // this shader is used for the fast sprite rendering - this.fastShader = null; - - // the next one is used for rendering triangle strips - this.stripShader = null; + this.initPlugins(); // listen for context and update necessary shaders var self = this; - this.renderer.on('context', function (event) + this.renderer.on('context', function () { - var gl = event.data; - - // this shader is used for rendering primitives - self.primitiveShader = new PrimitiveShader(gl); - - // this shader is used for rendering triangle strips - self.complexPrimitiveShader = new ComplexPrimitiveShader(gl); - - // this shader is used for the default sprite rendering - self.defaultShader = new Shader(gl); - - // this shader is used for the fast sprite rendering - self.fastShader = new FastShader(gl); - - // the next one is used for rendering triangle strips - self.stripShader = new StripShader(gl); + for (var o in this.plugins) + { + this.plugins[o] = new (this.plugins[o].constructor)(self); + } self.setShader(self.defaultShader); }); @@ -95,6 +66,8 @@ WebGLShaderManager.prototype.constructor = WebGLShaderManager; module.exports = WebGLShaderManager; +utils.pluginTarget.mixin(WebGLShaderManager); + /** * Takes the attributes given in parameters. * @@ -164,24 +137,11 @@ */ WebGLShaderManager.prototype.destroy = function () { + this.destroyPlugins(); + this.attribState = null; this.tempAttribState = null; - this.primitiveShader.destroy(); - this.primitiveShader = null; - - this.complexPrimitiveShader.destroy(); - this.complexPrimitiveShader = null; - - this.defaultShader.destroy(); - this.defaultShader = null; - - this.fastShader.destroy(); - this.fastShader = null; - - this.stripShader.destroy(); - this.stripShader = null; - this.renderer = null; }; diff --git a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js b/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js deleted file mode 100644 index f8a07da..0000000 --- a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js +++ /dev/null @@ -1,53 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function ComplexPrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform vec3 tint;', - 'uniform float alpha;', - 'uniform vec3 color;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - color: { type: '3f', value: [0,0,0] }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); -ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; -module.exports = ComplexPrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/FastShader.js b/src/core/renderers/webgl/shaders/FastShader.js index d198f10..c80d040 100644 --- a/src/core/renderers/webgl/shaders/FastShader.js +++ b/src/core/renderers/webgl/shaders/FastShader.js @@ -1,15 +1,16 @@ -var Shader = require('./Shader'); +var Shader = require('./Shader'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @extends Shader * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. */ -function FastShader(gl) +function FastShader(shaderManager) { Shader.call(this, - gl, + shaderManager, // vertex shader [ 'attribute vec2 aVertexPosition;', @@ -70,3 +71,5 @@ FastShader.prototype = Object.create(Shader.prototype); FastShader.prototype.constructor = FastShader; module.exports = FastShader; + +WebGLShaderManager.registerPlugin('fastShader', FastShader); diff --git a/src/core/renderers/webgl/shaders/PrimitiveShader.js b/src/core/renderers/webgl/shaders/PrimitiveShader.js deleted file mode 100644 index 753c475..0000000 --- a/src/core/renderers/webgl/shaders/PrimitiveShader.js +++ /dev/null @@ -1,55 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform float alpha;', - 'uniform float flipY;', - 'uniform vec3 tint;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aColor:0 - } - ); -} - -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; -module.exports = PrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/Shader.js b/src/core/renderers/webgl/shaders/Shader.js index d065a76..c60c5f9 100644 --- a/src/core/renderers/webgl/shaders/Shader.js +++ b/src/core/renderers/webgl/shaders/Shader.js @@ -1,12 +1,16 @@ -var utils = require('../../../utils'); +var utils = require('../../../utils'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @namespace PIXI - * @param [fragmentSrc] {string} The source of the fragment shader. + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. * @param [vertexSrc] {string} The source of the vertex shader. + * @param [fragmentSrc] {string} The source of the fragment shader. + * @param customUniforms {object} Custom uniforms to use to augment the built-in ones. + * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Shader(gl, vertexSrc, fragmentSrc, customUniforms, customAttributes) +function Shader(shaderManager, vertexSrc, fragmentSrc, customUniforms, customAttributes) { /** * @member {number} @@ -18,7 +22,7 @@ * @member {WebGLContext} * @readonly */ - this.gl = gl; + this.gl = shaderManager.renderer.gl; /** * The WebGL program. @@ -31,8 +35,8 @@ uSampler: { type: 'sampler2D', value: 0 }, projectionVector: { type: '2f', value: { x: 0, y: 0 } }, offsetVector: { type: '2f', value: { x: 0, y: 0 } }, - projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, - 0, 1, 0, + projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, + 0, 1, 0, 0, 0, 1) }, dimensions: { type: '4f', value: new Float32Array(4) } }; @@ -103,6 +107,8 @@ Shader.prototype.constructor = Shader; module.exports = Shader; +WebGLShaderManager.registerPlugin('defaultShader', Shader); + Shader.prototype.init = function () { this.compile(); @@ -506,4 +512,4 @@ } return shader; -}; \ No newline at end of file +}; diff --git a/src/core/renderers/webgl/shaders/SpriteShader.js b/src/core/renderers/webgl/shaders/SpriteShader.js deleted file mode 100644 index 1805b0a..0000000 --- a/src/core/renderers/webgl/shaders/SpriteShader.js +++ /dev/null @@ -1,42 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function SpriteShader(gl) -{ - Shader.call(this, - gl, - null, - // fragment shader - [ - 'precision lowp float;', - - 'varying vec2 vTextureCoord;', - 'varying vec4 vColor;', - - 'uniform sampler2D uSampler;', - - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aTextureCoord: 0, - aColor: 0 - } - ); -} - -SpriteShader.prototype = Object.create(Shader.prototype); -SpriteShader.prototype.constructor = SpriteShader; -module.exports = SpriteShader; diff --git a/src/core/renderers/webgl/shaders/StripShader.js b/src/core/renderers/webgl/shaders/StripShader.js deleted file mode 100644 index f487381..0000000 --- a/src/core/renderers/webgl/shaders/StripShader.js +++ /dev/null @@ -1,54 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function StripShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - // 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform vec2 projectionVector;', - 'uniform vec2 offsetVector;', - - 'varying vec2 vTextureCoord;', - - 'void main(void){', - ' vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);', - ' v -= offsetVector.xyx;', - ' gl_Position = vec4( v.x / projectionVector.x -1.0, v.y / -projectionVector.y + 1.0 , 0.0, 1.0);', - ' vTextureCoord = aTextureCoord;', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'uniform float alpha;', - 'uniform sampler2D uSampler;', - - 'varying vec2 vTextureCoord;', - - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y)) * alpha;', - '}' - ].join('\n'), - // custom uniforms - { - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -StripShader.prototype = Object.create(Shader.prototype); -StripShader.prototype.constructor = StripShader; -module.exports = StripShader; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index 0a34e55..43e2d00 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -33,7 +33,7 @@ * @property texture * @type Any */ - this.texture = null + this.texture = null; this.width = 0; this.height = 0; @@ -56,11 +56,11 @@ this.frameBuffer = gl.createFramebuffer(); - /* + /* A frame buffer needs a target to render to.. create a texture and bind it attach it to the framebuffer.. */ - + this.texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, this.texture); @@ -70,13 +70,13 @@ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); 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); - + gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer ); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0); - - /* + + /* The stencil buffer is used for masking in pixi lets create one and then add attach it to the framebuffer.. */ @@ -86,7 +86,7 @@ } this.resize(width, height); - + }; RenderTarget.prototype.constructor = RenderTarget; @@ -94,21 +94,21 @@ /** * Clears the filter texture. -* +* * @method clear */ RenderTarget.prototype.clear = function() { var gl = this.gl; - + gl.clearColor(0,0,0, 0); gl.clear(gl.COLOR_BUFFER_BIT); }; RenderTarget.prototype.activate = function() { - this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer ); -} + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer); +}; /** * Resizes the texture to the specified width and height @@ -119,25 +119,25 @@ */ RenderTarget.prototype.resize = function(width, height) { - width = width | 0; - height = height | 0; - - if(this.width === width && this.height === height) return; + if (this.width === width && this.height === height) { + return; + } this.width = width; this.height = height; + this.projectionMatrix = new math.Matrix(); - - if(!this.root) + + if (!this.root) { var gl = this.gl; this.projectionMatrix.a = 1/width*2; - this.projectionMatrix.d = 1/height*2; + this.projectionMatrix.d = -1/height*2; this.projectionMatrix.tx = -1; - this.projectionMatrix.ty = -1; + this.projectionMatrix.ty = 1; gl.bindTexture(gl.TEXTURE_2D, this.texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width , height , 0, gl.RGBA, gl.UNSIGNED_BYTE, null); @@ -157,7 +157,7 @@ /** * Destroys the filter texture. -* +* * @method destroy */ RenderTarget.prototype.destroy = function() diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js new file mode 100644 index 0000000..41be102 --- /dev/null +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -0,0 +1,48 @@ +/** + * @class + * @private + */ +function WebGLGraphicsData(gl) { + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +} + +WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; +module.exports = WebGLGraphicsData; + +/** + * + */ +WebGLGraphicsData.prototype.reset = function () { + this.points = []; + this.indices = []; +}; + +/** + * + */ +WebGLGraphicsData.prototype.upload = function () { + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; diff --git a/src/core/index.js b/src/core/index.js index 6f744e9..1d0a398 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -23,12 +23,12 @@ Sprite: require('./sprites/Sprite'), SpriteBatch: require('./sprites/SpriteBatch'), - SpriteRenderer: require('./sprites/SpriteRenderer'), + SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), // primitives - Graphics: require('./primitives/Graphics'), - GraphicsData: require('./primitives/GraphicsData'), - GraphicsRenderer: require('./primitives/GraphicsRenderer'), + Graphics: require('./graphics/Graphics'), + GraphicsData: require('./graphics/GraphicsData'), + GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,10 +42,8 @@ CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), // renderers - webgl - WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), - // WebGLGraphics: require('./renderers/webgl/utils/WebGLGraphics'), - - + WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), + Shader: require('./renderers/webgl/shaders/Shader'), /** * This helper function will automatically detect which renderer you should be using. @@ -103,7 +101,3 @@ return core.autoDetectRenderer(width, height, options, isAndroid); } }; - -// Adding here for now.. sure there is a better place! -core.WebGLRenderer.registerObjectRenderer('sprite', core.SpriteRenderer); -core.WebGLRenderer.registerObjectRenderer('graphics', core.GraphicsRenderer); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index a086950..81de05c 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -268,6 +268,6 @@ matrix.ty = this.ty; return matrix; -} +}; Matrix.IDENTITY = new Matrix(); diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js deleted file mode 100644 index 8db9b45..0000000 --- a/src/core/primitives/Graphics.js +++ /dev/null @@ -1,1116 +0,0 @@ -var DisplayObjectContainer = require('../display/DisplayObjectContainer'), - //Sprite = require('../display/Sprite'), - Texture = require('../textures/Texture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), - // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), - GraphicsData = require('./GraphicsData'), - math = require('../math'), - CONST = require('../const'); - -/** - * The Graphics class contains methods used to draw primitive shapes such as lines, circles and - * rectangles to the display, and color and fill them. - * - * @class - * @extends DisplayObjectContainer - * @namespace PIXI - */ -function Graphics() -{ - DisplayObjectContainer.call(this); - - this.renderable = true; - - /** - * The alpha value used when filling the Graphics object. - * - * @member {number} - * @default 1 - */ - this.fillAlpha = 1; - - /** - * The width (thickness) of any lines drawn. - * - * @member {number} - * @default 0 - */ - this.lineWidth = 0; - - /** - * The color of any lines drawn. - * - * @member {string} - * @default 0 - */ - this.lineColor = 0; - - /** - * Graphics data - * - * @member {GraphicsData[]} - * @private - */ - this.graphicsData = []; - - /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * Current path - * - * @member {GraphicsData} - * @private - */ - this.currentPath = null; - - /** - * Array containing some WebGL-related properties used by the WebGL renderer. - * - * @member {object} - * @private - */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; - - /** - * Whether this shape is being used as a mask. - * - * @member {boolean} - */ - this.isMask = false; - - /** - * The bounds' padding used for bounds calculation. - * - * @member {number} - */ - this.boundsPadding = 0; - - /** - * A cache of the local bounds to prevent recalculation. - * - * @member {Rectangle} - * @private - */ - this._localBounds = new math.Rectangle(0,0,1,1); - - /** - * Used to detect if the graphics object has changed. If this is set to true then the graphics - * object will be recalculated. - * - * @member {boolean} - * @private - */ - this.dirty = true; - - /** - * Used to detect if the WebGL graphics object has changed. If this is set to true then the - * graphics object will be recalculated. - * - * @member {boolean} - * @private - */ - this.glDirty = false; - - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; -} - -// constructor -Graphics.prototype = Object.create(DisplayObjectContainer.prototype); -Graphics.prototype.constructor = Graphics; -module.exports = Graphics; - -Object.defineProperties(Graphics.prototype, { - /** - * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. - * This is useful if your graphics element does not change often, as it will speed up the rendering - * of the object in exchange for taking up texture memory. It is also useful if you need the graphics - * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if - * you are constantly redrawing the graphics element. - * - * @member {boolean} - * @memberof Graphics# - * @default false - * @private - */ - cacheAsBitmap: { - get: function () - { - return this._cacheAsBitmap; - }, - set: function (value) - { - this._cacheAsBitmap = value; - - if (this._cacheAsBitmap) - { - this._generateCachedSprite(); - } - else - { - this.destroyCachedSprite(); - this.dirty = true; - } - } - } -}); - -/** - * Creates a new Graphics object with the same values as this one. - * - * @return {Graphics} - */ -GraphicsData.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = this.dirty; - clone.glDirty = this.glDirty; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData.clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {Graphics} - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (arguments.length < 3) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); - } - else - { - // otherwise its empty so lets just set the line properties - this.currentPath.lineWidth = this.lineWidth; - this.currentPath.lineColor = this.lineColor; - this.currentPath.lineAlpha = this.lineAlpha; - } - } - - return this; -}; - -/** - * Moves the current drawing position to x, y. - * - * @param x {number} the X coordinate to move to - * @param y {number} the Y coordinate to move to - * @return {Graphics} - */ -Graphics.prototype.moveTo = function (x, y) -{ - this.drawShape(new math.Polygon([x,y])); - - return this; -}; - -/** - * Draws a line using the current line style from the current drawing position to (x, y); - * The current drawing position is then set to (x, y). - * - * @param x {number} the X coordinate to draw to - * @param y {number} the Y coordinate to draw to - * @return {Graphics} - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a quadratic bezier curve and then draws it. - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @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 {Graphics} - */ -Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var xa, - ya, - n = 20, - points = this.currentPath.shape.points; - - if (points.length === 0) - { - this.moveTo(0, 0); - } - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - for (var i = 1; i <= n; ++i) - { - j = i / n; - - xa = fromX + ( (cpX - fromX) * j ); - ya = fromY + ( (cpY - fromY) * j ); - - points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), - ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); - } - - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a bezier curve and then draws it. - * - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param cpX2 {number} Second Control point x - * @param cpY2 {number} Second Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {Graphics} - */ -Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var n = 20, - dt, - dt2, - dt3, - t2, - t3, - points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - - for (var i = 1; i <= n; ++i) - { - j = i / n; - - dt = (1 - j); - dt2 = dt * dt; - dt3 = dt2 * dt; - - t2 = j * j; - t3 = t2 * j; - - points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, - dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); - } - - this.dirty = true; - - return this; -}; - -/** - * The arcTo() method creates an arc/curve between two tangents on the canvas. - * - * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! - * - * @param x1 {number} The x-coordinate of the beginning of the arc - * @param y1 {number} The y-coordinate of the beginning of the arc - * @param x2 {number} The x-coordinate of the end of the arc - * @param y2 {number} The y-coordinate of the end of the arc - * @param radius {number} The radius of the arc - * @return {Graphics} - */ -Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points.push(x1, y1); - } - } - else - { - this.moveTo(x1, y1); - } - - var points = this.currentPath.shape.points, - fromX = points[points.length-2], - fromY = points[points.length-1], - a1 = fromY - y1, - b1 = fromX - x1, - a2 = y2 - y1, - b2 = x2 - x1, - mm = Math.abs(a1 * b2 - b1 * a2); - - if (mm < 1.0e-8 || radius === 0) - { - if (points[points.length-2] !== x1 || points[points.length-1] !== y1) - { - points.push(x1, y1); - } - } - else - { - var dd = a1 * a1 + b1 * b1, - cc = a2 * a2 + b2 * b2, - tt = a1 * a2 + b1 * b2, - k1 = radius * Math.sqrt(dd) / mm, - k2 = radius * Math.sqrt(cc) / mm, - j1 = k1 * tt / dd, - j2 = k2 * tt / cc, - cx = k1 * b2 + k2 * b1, - cy = k1 * a2 + k2 * a1, - px = b1 * (k2 + j1), - py = a1 * (k2 + j1), - qx = b2 * (k1 + j2), - qy = a2 * (k1 + j2), - startAngle = Math.atan2(py - cy, px - cx), - endAngle = Math.atan2(qy - cy, qx - cx); - - this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); - } - - this.dirty = true; - - return this; -}; - -/** - * The arc method creates an arc/curve (used to create circles, or parts of circles). - * - * @param cx {number} The x-coordinate of the center of the circle - * @param cy {number} The y-coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) - * @param endAngle {number} The ending angle, in radians - * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {Graphics} - */ -Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - var points; - - // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... - if (this.currentPath) - { - points = this.currentPath.shape.points; - - if (points.length === 0) - { - points.push(startX, startY); - } - else if (points[points.length-2] !== startX || points[points.length-1] !== startY) - { - points.push(startX, startY); - } - } - else - { - this.moveTo(startX, startY); - points = this.currentPath.shape.points; - } - - if (startAngle === endAngle) - { - return this; - } - - if (!anticlockwise && endAngle <= startAngle) - { - endAngle += Math.PI * 2; - } - else if (anticlockwise && startAngle <= endAngle) - { - startAngle += Math.PI * 2; - } - - var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); - var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; - - if (sweep === 0) - { - return this; - } - - var theta = sweep/(segs*2); - var theta2 = theta*2; - - var cTheta = Math.cos(theta); - var sTheta = Math.sin(theta); - - var segMinus = segs - 1; - - var remainder = ( segMinus % 1 ) / segMinus; - - for (var i = 0; i <= segMinus; ++i) - { - var real = i + remainder * i; - var angle = ((theta) + startAngle + (theta2 * real)); - - var c = Math.cos(angle); - var s = -Math.sin(angle); - - points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, - ( (cTheta * -s) + (sTheta * c) ) * radius + cy); - } - - this.dirty = true; - - return this; -}; - -/** - * Specifies a simple one-color fill that subsequent calls to other Graphics methods - * (such as lineTo() or drawCircle()) use when drawing. - * - * @param color {number} the color of the fill - * @param alpha {number} the alpha of the fill - * @return {Graphics} - */ -Graphics.prototype.beginFill = function (color, alpha) -{ - this.filling = true; - this.fillColor = color || 0; - this.fillAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length <= 2) - { - this.currentPath.fill = this.filling; - this.currentPath.fillColor = this.fillColor; - this.currentPath.fillAlpha = this.fillAlpha; - } - } - return this; -}; - -/** - * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. - * - * @return {Graphics} - */ -Graphics.prototype.endFill = function () -{ - this.filling = false; - this.fillColor = null; - this.fillAlpha = 1; - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @return {Graphics} - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {Graphics} - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {Graphics} - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {Array} The path data used to construct the polygon. - * @return {Graphics} - */ -Graphics.prototype.drawPolygon = function (path) -{ - if (!(path instanceof Array)) - { - path = Array.prototype.slice.call(arguments); - } - - this.drawShape(new math.Polygon(path)); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {Graphics} - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty = true; - this.clearDirty = true; - this.graphicsData = []; - - return this; -}; - -/** - * Useful function that returns a texture of the graphics object that can then be used to create sprites - * This can be quite useful if your geometry is complicated and needs to be reused multiple times. - * - * @param resolution {number} The resolution of the texture being generated - * @param scaleMode {number} Should be one of the scaleMode consts - * @return {Texture} a texture of the graphics object - */ -Graphics.prototype.generateTexture = function (resolution, scaleMode) -{ - resolution = resolution || 1; - - var bounds = this.getBounds(); - - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); - - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - canvasBuffer.context.scale(resolution, resolution); - - canvasBuffer.context.translate(-bounds.x,-bounds.y); - - CanvasGraphics.renderGraphics(this, canvasBuffer.context); - - return texture; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (this.isMask === true) - { - return; - } - - // this code may still be needed so leaving for now.. - // - /* - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture on the gpu too! - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.worldAlpha = this.worldAlpha; - - Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); - - return; - } - - */ - - if (this.glDirty) - { - this.dirty = true; - this.glDirty = false; - } - - renderer.setObjectRenderer(renderer.objectRenderers.graphics); - renderer.objectRenderers.graphics.render(this); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} - * @private - */ -Graphics.prototype.renderCanvas = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.alpha <= 0 || this.isMask === true) - { - return; - } - - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.alpha = this.alpha; - - Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); - - return; - } - else - { - var context = renderer.context; - var transform = this.worldTransform; - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - var resolution = renderer.resolution; - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } - } -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @return {Rectangle} the rectangular bounding area - */ -Graphics.prototype.getBounds = function (matrix) -{ - // return an empty object if the item is a mask! - if (this.isMask) - { - return math.Rectangle.EMPTY; - } - - if (this.dirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.dirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - return this._bounds; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; - - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; -}; - -/** - * Generates the cached sprite when the sprite has cacheAsBitmap = true - * - * @private - */ -Graphics.prototype._generateCachedSprite = function () -{ - var bounds = this.getLocalBounds(); - - if (!this._cachedSprite) - { - var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); - var texture = Texture.fromCanvas(canvasBuffer.canvas); - - this._cachedSprite = new Sprite(texture); - this._cachedSprite.buffer = canvasBuffer; - - this._cachedSprite.worldTransform = this.worldTransform; - } - else - { - this._cachedSprite.buffer.resize(bounds.width, bounds.height); - } - - // leverage the anchor to account for the offset of the element - this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); - this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); - - // this._cachedSprite.buffer.context.save(); - this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); - - // make sure we set the alpha of the graphics to 1 for the render.. - this.worldAlpha = 1; - - // now render the graphic.. - CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); - - this._cachedSprite.alpha = this.alpha; -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Graphics.prototype.updateCachedSpriteTexture = function () -{ - var cachedSprite = this._cachedSprite; - var texture = cachedSprite.texture; - var canvas = cachedSprite.buffer.canvas; - - texture.baseTexture.width = canvas.width; - texture.baseTexture.height = canvas.height; - texture.crop.width = texture.frame.width = canvas.width; - texture.crop.height = texture.frame.height = canvas.height; - - cachedSprite._width = canvas.width; - cachedSprite._height = canvas.height; - - // update the dirty base textures - texture.baseTexture.dirty(); -}; - -/** - * Destroys a previous cached sprite. - * - */ -Graphics.prototype.destroyCachedSprite = function () -{ - this._cachedSprite.texture.destroy(true); - - // let the gc collect the unused sprite - // TODO could be object pooled! - this._cachedSprite = null; -}; - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. - * @return {GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = this.filling; - this.currentPath = data; - } - - this.dirty = true; - - return data; -}; diff --git a/src/core/primitives/GraphicsData.js b/src/core/primitives/GraphicsData.js deleted file mode 100644 index 788f6f2..0000000 --- a/src/core/primitives/GraphicsData.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * A GraphicsData object. - * - * @class - * @namespace PIXI - */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - this.lineWidth = lineWidth; - this.lineColor = lineColor; - this.lineAlpha = lineAlpha; - this._lineTint = lineColor; - - this.fillColor = fillColor; - this.fillAlpha = fillAlpha; - this._fillTint = fillColor; - this.fill = fill; - - this.shape = shape; - this.type = shape.type; -} - -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {GraphicsData} - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js deleted file mode 100644 index 1a508f9..0000000 --- a/src/core/primitives/GraphicsRenderer.js +++ /dev/null @@ -1,874 +0,0 @@ -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; - -/** - * 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 ); - -// var matrix = graphics.worldTransform.clone(); -// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); -// matrix.append(graphics.worldTransform); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.maskManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.maskManager.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.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); - - 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/primitives/WebGLGraphicsData.js b/src/core/primitives/WebGLGraphicsData.js deleted file mode 100755 index 41be102..0000000 --- a/src/core/primitives/WebGLGraphicsData.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @class - * @private - */ -function WebGLGraphicsData(gl) { - this.gl = gl; - - //TODO does this need to be split before uploding?? - this.color = [0,0,0]; // color split! - this.points = []; - this.indices = []; - this.buffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - this.mode = 1; - this.alpha = 1; - this.dirty = true; -} - -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; -module.exports = WebGLGraphicsData; - -/** - * - */ -WebGLGraphicsData.prototype.reset = function () { - this.points = []; - this.indices = []; -}; - -/** - * - */ -WebGLGraphicsData.prototype.upload = function () { - var gl = this.gl; - -// this.lastIndex = graphics.graphicsData.length; - this.glPoints = new Float32Array(this.points); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); - - this.glIndicies = new Uint16Array(this.indices); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); - - this.dirty = false; -}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6c1fb7d..4e82baa 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -174,6 +174,9 @@ // time to create the render managers! each one focuses on managing a state in webGL + // initialize the context so it is ready for the managers. + this._initContext(); + /** * Deals with managing the shader programs and their attribs * @member {WebGLShaderManager} @@ -204,17 +207,11 @@ */ this.blendModeManager = new WebGLBlendModeManager(this); - - this.blendModes = null; this._boundUpdateTexture = this.updateTexture.bind(this); this._boundDestroyTexture = this.destroyTexture.bind(this); - - - // time init the context.. - this._initContext(); this.currentRenderTarget = this.renderTarget; @@ -228,27 +225,18 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - /** - * Manages the renderer of specific objects. - * - * @member {object} - */ - this.objectRenderers = {}; - this.currentRenderer = new ObjectRenderer(); - - // create an instance of each registered object renderer - for (var o in WebGLRenderer._objectRenderers) { - this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); - } - + this.initPlugins(); } // constructor WebGLRenderer.prototype.constructor = WebGLRenderer; module.exports = WebGLRenderer; +WebGLRenderer.glContextId = 0; + +utils.pluginTarget.mixin(WebGLRenderer); utils.eventTarget.mixin(WebGLRenderer.prototype); Object.defineProperties(WebGLRenderer.prototype, { @@ -297,11 +285,9 @@ gl.enable(gl.BLEND); this.renderTarget = new RenderTarget(this.gl, this.width, this.height, null, true); - this.emit('context', gl); - // now resize and we are good to go! this.resize(this.width, this.height); }; @@ -349,7 +335,7 @@ gl.clear(gl.COLOR_BUFFER_BIT); } - this.renderDisplayObject(object, this.renderTarget)//this.projection); + this.renderDisplayObject(object, this.renderTarget);//this.projection); }; /** @@ -536,12 +522,7 @@ this.maskManager.destroy(); this.filterManager.destroy(); - for (var o in this.objectRenderers) { - this.objectRenderers[o].destroy(); - this.objectRenderers[o] = null; - } - - this.objectRenderers = null; + this.destroyPlugins(); // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -611,11 +592,3 @@ this.blendModes[CONST.blendModes.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; } }; - -WebGLRenderer.glContextId = 0; -WebGLRenderer._objectRenderers = {}; - -WebGLRenderer.registerObjectRenderer = function (name, ctor) { - WebGLRenderer._objectRenderers[name] = ctor; -}; - diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js index 87c99f0..d6ed3c6 100644 --- a/src/core/renderers/webgl/managers/WebGLFilterManager.js +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -51,7 +51,7 @@ */ WebGLFilterManager.prototype.begin = function (buffer) { - this.defaultShader = this.renderer.shaderManager.defaultShader; + this.defaultShader = this.renderer.shaderManager.plugins.defaultShader; this.width = this.renderer.projection.x * 2; this.height = -this.renderer.projection.y * 2; @@ -385,7 +385,7 @@ if (!shader) { - shader = new Shader(gl); + shader = new Shader(this.renderer.shaderManager); shader.fragmentSrc = filter.fragmentSrc; shader.uniforms = filter.uniforms; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js index 6a1f782..d6a11c1 100644 --- a/src/core/renderers/webgl/managers/WebGLMaskManager.js +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -126,11 +126,11 @@ // bind the graphics object.. var projection = this.renderer.projection, offset = this.renderer.offset, - shader;// = this.renderer.shaderManager.primitiveShader; + shader;// = this.renderer.shaderManager.plugins.primitiveShader; if (webGLData.mode === 1) { - shader = this.renderer.shaderManager.complexPrimitiveShader; + shader = this.renderer.shaderManager.plugins.complexPrimitiveShader; this.renderer.shaderManager.setShader(shader); @@ -156,7 +156,7 @@ else { //this.renderer.shaderManager.activatePrimitiveShader(); - shader = this.renderer.shaderManager.primitiveShader; + shader = this.renderer.shaderManager.plugins.primitiveShader; this.renderer.shaderManager.setShader( shader ); gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); @@ -292,11 +292,11 @@ */ WebGLMaskManager.prototype.pushMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); if (maskData.dirty) { - this.renderer.objectRenderers.graphics.updateGraphics(maskData, this.renderer.gl); + this.renderer.plugins.graphics.updateGraphics(maskData, this.renderer.gl); } if (!maskData._webGL[this.renderer.gl.id].data.length) @@ -314,7 +314,7 @@ */ WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); this.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); }; diff --git a/src/core/renderers/webgl/managers/WebGLShaderManager.js b/src/core/renderers/webgl/managers/WebGLShaderManager.js index 90c6477..9ebb58e 100644 --- a/src/core/renderers/webgl/managers/WebGLShaderManager.js +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -1,9 +1,5 @@ var WebGLManager = require('./WebGLManager'), - PrimitiveShader = require('../shaders/PrimitiveShader'), - ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), - Shader = require('../shaders/Shader'), - FastShader = require('../shaders/FastShader'), - StripShader = require('../shaders/StripShader'); + utils = require('../../../utils'); /** * @class @@ -51,41 +47,16 @@ */ this.currentShader = null; - // this shader is used for rendering primitives - this.primitiveShader = null; - - // this shader is used for rendering triangle strips - this.complexPrimitiveShader = null; - - // this shader is used for the default sprite rendering - this.defaultShader = null; - - // this shader is used for the fast sprite rendering - this.fastShader = null; - - // the next one is used for rendering triangle strips - this.stripShader = null; + this.initPlugins(); // listen for context and update necessary shaders var self = this; - this.renderer.on('context', function (event) + this.renderer.on('context', function () { - var gl = event.data; - - // this shader is used for rendering primitives - self.primitiveShader = new PrimitiveShader(gl); - - // this shader is used for rendering triangle strips - self.complexPrimitiveShader = new ComplexPrimitiveShader(gl); - - // this shader is used for the default sprite rendering - self.defaultShader = new Shader(gl); - - // this shader is used for the fast sprite rendering - self.fastShader = new FastShader(gl); - - // the next one is used for rendering triangle strips - self.stripShader = new StripShader(gl); + for (var o in this.plugins) + { + this.plugins[o] = new (this.plugins[o].constructor)(self); + } self.setShader(self.defaultShader); }); @@ -95,6 +66,8 @@ WebGLShaderManager.prototype.constructor = WebGLShaderManager; module.exports = WebGLShaderManager; +utils.pluginTarget.mixin(WebGLShaderManager); + /** * Takes the attributes given in parameters. * @@ -164,24 +137,11 @@ */ WebGLShaderManager.prototype.destroy = function () { + this.destroyPlugins(); + this.attribState = null; this.tempAttribState = null; - this.primitiveShader.destroy(); - this.primitiveShader = null; - - this.complexPrimitiveShader.destroy(); - this.complexPrimitiveShader = null; - - this.defaultShader.destroy(); - this.defaultShader = null; - - this.fastShader.destroy(); - this.fastShader = null; - - this.stripShader.destroy(); - this.stripShader = null; - this.renderer = null; }; diff --git a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js b/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js deleted file mode 100644 index f8a07da..0000000 --- a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js +++ /dev/null @@ -1,53 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function ComplexPrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform vec3 tint;', - 'uniform float alpha;', - 'uniform vec3 color;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - color: { type: '3f', value: [0,0,0] }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); -ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; -module.exports = ComplexPrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/FastShader.js b/src/core/renderers/webgl/shaders/FastShader.js index d198f10..c80d040 100644 --- a/src/core/renderers/webgl/shaders/FastShader.js +++ b/src/core/renderers/webgl/shaders/FastShader.js @@ -1,15 +1,16 @@ -var Shader = require('./Shader'); +var Shader = require('./Shader'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @extends Shader * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. */ -function FastShader(gl) +function FastShader(shaderManager) { Shader.call(this, - gl, + shaderManager, // vertex shader [ 'attribute vec2 aVertexPosition;', @@ -70,3 +71,5 @@ FastShader.prototype = Object.create(Shader.prototype); FastShader.prototype.constructor = FastShader; module.exports = FastShader; + +WebGLShaderManager.registerPlugin('fastShader', FastShader); diff --git a/src/core/renderers/webgl/shaders/PrimitiveShader.js b/src/core/renderers/webgl/shaders/PrimitiveShader.js deleted file mode 100644 index 753c475..0000000 --- a/src/core/renderers/webgl/shaders/PrimitiveShader.js +++ /dev/null @@ -1,55 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform float alpha;', - 'uniform float flipY;', - 'uniform vec3 tint;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aColor:0 - } - ); -} - -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; -module.exports = PrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/Shader.js b/src/core/renderers/webgl/shaders/Shader.js index d065a76..c60c5f9 100644 --- a/src/core/renderers/webgl/shaders/Shader.js +++ b/src/core/renderers/webgl/shaders/Shader.js @@ -1,12 +1,16 @@ -var utils = require('../../../utils'); +var utils = require('../../../utils'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @namespace PIXI - * @param [fragmentSrc] {string} The source of the fragment shader. + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. * @param [vertexSrc] {string} The source of the vertex shader. + * @param [fragmentSrc] {string} The source of the fragment shader. + * @param customUniforms {object} Custom uniforms to use to augment the built-in ones. + * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Shader(gl, vertexSrc, fragmentSrc, customUniforms, customAttributes) +function Shader(shaderManager, vertexSrc, fragmentSrc, customUniforms, customAttributes) { /** * @member {number} @@ -18,7 +22,7 @@ * @member {WebGLContext} * @readonly */ - this.gl = gl; + this.gl = shaderManager.renderer.gl; /** * The WebGL program. @@ -31,8 +35,8 @@ uSampler: { type: 'sampler2D', value: 0 }, projectionVector: { type: '2f', value: { x: 0, y: 0 } }, offsetVector: { type: '2f', value: { x: 0, y: 0 } }, - projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, - 0, 1, 0, + projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, + 0, 1, 0, 0, 0, 1) }, dimensions: { type: '4f', value: new Float32Array(4) } }; @@ -103,6 +107,8 @@ Shader.prototype.constructor = Shader; module.exports = Shader; +WebGLShaderManager.registerPlugin('defaultShader', Shader); + Shader.prototype.init = function () { this.compile(); @@ -506,4 +512,4 @@ } return shader; -}; \ No newline at end of file +}; diff --git a/src/core/renderers/webgl/shaders/SpriteShader.js b/src/core/renderers/webgl/shaders/SpriteShader.js deleted file mode 100644 index 1805b0a..0000000 --- a/src/core/renderers/webgl/shaders/SpriteShader.js +++ /dev/null @@ -1,42 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function SpriteShader(gl) -{ - Shader.call(this, - gl, - null, - // fragment shader - [ - 'precision lowp float;', - - 'varying vec2 vTextureCoord;', - 'varying vec4 vColor;', - - 'uniform sampler2D uSampler;', - - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aTextureCoord: 0, - aColor: 0 - } - ); -} - -SpriteShader.prototype = Object.create(Shader.prototype); -SpriteShader.prototype.constructor = SpriteShader; -module.exports = SpriteShader; diff --git a/src/core/renderers/webgl/shaders/StripShader.js b/src/core/renderers/webgl/shaders/StripShader.js deleted file mode 100644 index f487381..0000000 --- a/src/core/renderers/webgl/shaders/StripShader.js +++ /dev/null @@ -1,54 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function StripShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - // 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform vec2 projectionVector;', - 'uniform vec2 offsetVector;', - - 'varying vec2 vTextureCoord;', - - 'void main(void){', - ' vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);', - ' v -= offsetVector.xyx;', - ' gl_Position = vec4( v.x / projectionVector.x -1.0, v.y / -projectionVector.y + 1.0 , 0.0, 1.0);', - ' vTextureCoord = aTextureCoord;', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'uniform float alpha;', - 'uniform sampler2D uSampler;', - - 'varying vec2 vTextureCoord;', - - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y)) * alpha;', - '}' - ].join('\n'), - // custom uniforms - { - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -StripShader.prototype = Object.create(Shader.prototype); -StripShader.prototype.constructor = StripShader; -module.exports = StripShader; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index 0a34e55..43e2d00 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -33,7 +33,7 @@ * @property texture * @type Any */ - this.texture = null + this.texture = null; this.width = 0; this.height = 0; @@ -56,11 +56,11 @@ this.frameBuffer = gl.createFramebuffer(); - /* + /* A frame buffer needs a target to render to.. create a texture and bind it attach it to the framebuffer.. */ - + this.texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, this.texture); @@ -70,13 +70,13 @@ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); 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); - + gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer ); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0); - - /* + + /* The stencil buffer is used for masking in pixi lets create one and then add attach it to the framebuffer.. */ @@ -86,7 +86,7 @@ } this.resize(width, height); - + }; RenderTarget.prototype.constructor = RenderTarget; @@ -94,21 +94,21 @@ /** * Clears the filter texture. -* +* * @method clear */ RenderTarget.prototype.clear = function() { var gl = this.gl; - + gl.clearColor(0,0,0, 0); gl.clear(gl.COLOR_BUFFER_BIT); }; RenderTarget.prototype.activate = function() { - this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer ); -} + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer); +}; /** * Resizes the texture to the specified width and height @@ -119,25 +119,25 @@ */ RenderTarget.prototype.resize = function(width, height) { - width = width | 0; - height = height | 0; - - if(this.width === width && this.height === height) return; + if (this.width === width && this.height === height) { + return; + } this.width = width; this.height = height; + this.projectionMatrix = new math.Matrix(); - - if(!this.root) + + if (!this.root) { var gl = this.gl; this.projectionMatrix.a = 1/width*2; - this.projectionMatrix.d = 1/height*2; + this.projectionMatrix.d = -1/height*2; this.projectionMatrix.tx = -1; - this.projectionMatrix.ty = -1; + this.projectionMatrix.ty = 1; gl.bindTexture(gl.TEXTURE_2D, this.texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width , height , 0, gl.RGBA, gl.UNSIGNED_BYTE, null); @@ -157,7 +157,7 @@ /** * Destroys the filter texture. -* +* * @method destroy */ RenderTarget.prototype.destroy = function() diff --git a/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js b/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js index a63f4d7..3f37b7d 100644 --- a/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js +++ b/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js @@ -183,7 +183,7 @@ */ WebGLFastSpriteBatch.prototype.begin = function (spriteBatch) { - this.shader = this.renderer.shaderManager.fastShader; + this.shader = this.renderer.shaderManager.plugins.fastShader; this.matrix = spriteBatch.worldTransform.toArray(true); diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js new file mode 100644 index 0000000..41be102 --- /dev/null +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -0,0 +1,48 @@ +/** + * @class + * @private + */ +function WebGLGraphicsData(gl) { + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +} + +WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; +module.exports = WebGLGraphicsData; + +/** + * + */ +WebGLGraphicsData.prototype.reset = function () { + this.points = []; + this.indices = []; +}; + +/** + * + */ +WebGLGraphicsData.prototype.upload = function () { + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; diff --git a/src/core/index.js b/src/core/index.js index 6f744e9..1d0a398 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -23,12 +23,12 @@ Sprite: require('./sprites/Sprite'), SpriteBatch: require('./sprites/SpriteBatch'), - SpriteRenderer: require('./sprites/SpriteRenderer'), + SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), // primitives - Graphics: require('./primitives/Graphics'), - GraphicsData: require('./primitives/GraphicsData'), - GraphicsRenderer: require('./primitives/GraphicsRenderer'), + Graphics: require('./graphics/Graphics'), + GraphicsData: require('./graphics/GraphicsData'), + GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,10 +42,8 @@ CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), // renderers - webgl - WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), - // WebGLGraphics: require('./renderers/webgl/utils/WebGLGraphics'), - - + WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), + Shader: require('./renderers/webgl/shaders/Shader'), /** * This helper function will automatically detect which renderer you should be using. @@ -103,7 +101,3 @@ return core.autoDetectRenderer(width, height, options, isAndroid); } }; - -// Adding here for now.. sure there is a better place! -core.WebGLRenderer.registerObjectRenderer('sprite', core.SpriteRenderer); -core.WebGLRenderer.registerObjectRenderer('graphics', core.GraphicsRenderer); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index a086950..81de05c 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -268,6 +268,6 @@ matrix.ty = this.ty; return matrix; -} +}; Matrix.IDENTITY = new Matrix(); diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js deleted file mode 100644 index 8db9b45..0000000 --- a/src/core/primitives/Graphics.js +++ /dev/null @@ -1,1116 +0,0 @@ -var DisplayObjectContainer = require('../display/DisplayObjectContainer'), - //Sprite = require('../display/Sprite'), - Texture = require('../textures/Texture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), - // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), - GraphicsData = require('./GraphicsData'), - math = require('../math'), - CONST = require('../const'); - -/** - * The Graphics class contains methods used to draw primitive shapes such as lines, circles and - * rectangles to the display, and color and fill them. - * - * @class - * @extends DisplayObjectContainer - * @namespace PIXI - */ -function Graphics() -{ - DisplayObjectContainer.call(this); - - this.renderable = true; - - /** - * The alpha value used when filling the Graphics object. - * - * @member {number} - * @default 1 - */ - this.fillAlpha = 1; - - /** - * The width (thickness) of any lines drawn. - * - * @member {number} - * @default 0 - */ - this.lineWidth = 0; - - /** - * The color of any lines drawn. - * - * @member {string} - * @default 0 - */ - this.lineColor = 0; - - /** - * Graphics data - * - * @member {GraphicsData[]} - * @private - */ - this.graphicsData = []; - - /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * Current path - * - * @member {GraphicsData} - * @private - */ - this.currentPath = null; - - /** - * Array containing some WebGL-related properties used by the WebGL renderer. - * - * @member {object} - * @private - */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; - - /** - * Whether this shape is being used as a mask. - * - * @member {boolean} - */ - this.isMask = false; - - /** - * The bounds' padding used for bounds calculation. - * - * @member {number} - */ - this.boundsPadding = 0; - - /** - * A cache of the local bounds to prevent recalculation. - * - * @member {Rectangle} - * @private - */ - this._localBounds = new math.Rectangle(0,0,1,1); - - /** - * Used to detect if the graphics object has changed. If this is set to true then the graphics - * object will be recalculated. - * - * @member {boolean} - * @private - */ - this.dirty = true; - - /** - * Used to detect if the WebGL graphics object has changed. If this is set to true then the - * graphics object will be recalculated. - * - * @member {boolean} - * @private - */ - this.glDirty = false; - - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; -} - -// constructor -Graphics.prototype = Object.create(DisplayObjectContainer.prototype); -Graphics.prototype.constructor = Graphics; -module.exports = Graphics; - -Object.defineProperties(Graphics.prototype, { - /** - * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. - * This is useful if your graphics element does not change often, as it will speed up the rendering - * of the object in exchange for taking up texture memory. It is also useful if you need the graphics - * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if - * you are constantly redrawing the graphics element. - * - * @member {boolean} - * @memberof Graphics# - * @default false - * @private - */ - cacheAsBitmap: { - get: function () - { - return this._cacheAsBitmap; - }, - set: function (value) - { - this._cacheAsBitmap = value; - - if (this._cacheAsBitmap) - { - this._generateCachedSprite(); - } - else - { - this.destroyCachedSprite(); - this.dirty = true; - } - } - } -}); - -/** - * Creates a new Graphics object with the same values as this one. - * - * @return {Graphics} - */ -GraphicsData.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = this.dirty; - clone.glDirty = this.glDirty; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData.clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {Graphics} - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (arguments.length < 3) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); - } - else - { - // otherwise its empty so lets just set the line properties - this.currentPath.lineWidth = this.lineWidth; - this.currentPath.lineColor = this.lineColor; - this.currentPath.lineAlpha = this.lineAlpha; - } - } - - return this; -}; - -/** - * Moves the current drawing position to x, y. - * - * @param x {number} the X coordinate to move to - * @param y {number} the Y coordinate to move to - * @return {Graphics} - */ -Graphics.prototype.moveTo = function (x, y) -{ - this.drawShape(new math.Polygon([x,y])); - - return this; -}; - -/** - * Draws a line using the current line style from the current drawing position to (x, y); - * The current drawing position is then set to (x, y). - * - * @param x {number} the X coordinate to draw to - * @param y {number} the Y coordinate to draw to - * @return {Graphics} - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a quadratic bezier curve and then draws it. - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @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 {Graphics} - */ -Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var xa, - ya, - n = 20, - points = this.currentPath.shape.points; - - if (points.length === 0) - { - this.moveTo(0, 0); - } - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - for (var i = 1; i <= n; ++i) - { - j = i / n; - - xa = fromX + ( (cpX - fromX) * j ); - ya = fromY + ( (cpY - fromY) * j ); - - points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), - ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); - } - - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a bezier curve and then draws it. - * - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param cpX2 {number} Second Control point x - * @param cpY2 {number} Second Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {Graphics} - */ -Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var n = 20, - dt, - dt2, - dt3, - t2, - t3, - points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - - for (var i = 1; i <= n; ++i) - { - j = i / n; - - dt = (1 - j); - dt2 = dt * dt; - dt3 = dt2 * dt; - - t2 = j * j; - t3 = t2 * j; - - points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, - dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); - } - - this.dirty = true; - - return this; -}; - -/** - * The arcTo() method creates an arc/curve between two tangents on the canvas. - * - * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! - * - * @param x1 {number} The x-coordinate of the beginning of the arc - * @param y1 {number} The y-coordinate of the beginning of the arc - * @param x2 {number} The x-coordinate of the end of the arc - * @param y2 {number} The y-coordinate of the end of the arc - * @param radius {number} The radius of the arc - * @return {Graphics} - */ -Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points.push(x1, y1); - } - } - else - { - this.moveTo(x1, y1); - } - - var points = this.currentPath.shape.points, - fromX = points[points.length-2], - fromY = points[points.length-1], - a1 = fromY - y1, - b1 = fromX - x1, - a2 = y2 - y1, - b2 = x2 - x1, - mm = Math.abs(a1 * b2 - b1 * a2); - - if (mm < 1.0e-8 || radius === 0) - { - if (points[points.length-2] !== x1 || points[points.length-1] !== y1) - { - points.push(x1, y1); - } - } - else - { - var dd = a1 * a1 + b1 * b1, - cc = a2 * a2 + b2 * b2, - tt = a1 * a2 + b1 * b2, - k1 = radius * Math.sqrt(dd) / mm, - k2 = radius * Math.sqrt(cc) / mm, - j1 = k1 * tt / dd, - j2 = k2 * tt / cc, - cx = k1 * b2 + k2 * b1, - cy = k1 * a2 + k2 * a1, - px = b1 * (k2 + j1), - py = a1 * (k2 + j1), - qx = b2 * (k1 + j2), - qy = a2 * (k1 + j2), - startAngle = Math.atan2(py - cy, px - cx), - endAngle = Math.atan2(qy - cy, qx - cx); - - this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); - } - - this.dirty = true; - - return this; -}; - -/** - * The arc method creates an arc/curve (used to create circles, or parts of circles). - * - * @param cx {number} The x-coordinate of the center of the circle - * @param cy {number} The y-coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) - * @param endAngle {number} The ending angle, in radians - * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {Graphics} - */ -Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - var points; - - // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... - if (this.currentPath) - { - points = this.currentPath.shape.points; - - if (points.length === 0) - { - points.push(startX, startY); - } - else if (points[points.length-2] !== startX || points[points.length-1] !== startY) - { - points.push(startX, startY); - } - } - else - { - this.moveTo(startX, startY); - points = this.currentPath.shape.points; - } - - if (startAngle === endAngle) - { - return this; - } - - if (!anticlockwise && endAngle <= startAngle) - { - endAngle += Math.PI * 2; - } - else if (anticlockwise && startAngle <= endAngle) - { - startAngle += Math.PI * 2; - } - - var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); - var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; - - if (sweep === 0) - { - return this; - } - - var theta = sweep/(segs*2); - var theta2 = theta*2; - - var cTheta = Math.cos(theta); - var sTheta = Math.sin(theta); - - var segMinus = segs - 1; - - var remainder = ( segMinus % 1 ) / segMinus; - - for (var i = 0; i <= segMinus; ++i) - { - var real = i + remainder * i; - var angle = ((theta) + startAngle + (theta2 * real)); - - var c = Math.cos(angle); - var s = -Math.sin(angle); - - points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, - ( (cTheta * -s) + (sTheta * c) ) * radius + cy); - } - - this.dirty = true; - - return this; -}; - -/** - * Specifies a simple one-color fill that subsequent calls to other Graphics methods - * (such as lineTo() or drawCircle()) use when drawing. - * - * @param color {number} the color of the fill - * @param alpha {number} the alpha of the fill - * @return {Graphics} - */ -Graphics.prototype.beginFill = function (color, alpha) -{ - this.filling = true; - this.fillColor = color || 0; - this.fillAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length <= 2) - { - this.currentPath.fill = this.filling; - this.currentPath.fillColor = this.fillColor; - this.currentPath.fillAlpha = this.fillAlpha; - } - } - return this; -}; - -/** - * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. - * - * @return {Graphics} - */ -Graphics.prototype.endFill = function () -{ - this.filling = false; - this.fillColor = null; - this.fillAlpha = 1; - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @return {Graphics} - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {Graphics} - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {Graphics} - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {Array} The path data used to construct the polygon. - * @return {Graphics} - */ -Graphics.prototype.drawPolygon = function (path) -{ - if (!(path instanceof Array)) - { - path = Array.prototype.slice.call(arguments); - } - - this.drawShape(new math.Polygon(path)); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {Graphics} - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty = true; - this.clearDirty = true; - this.graphicsData = []; - - return this; -}; - -/** - * Useful function that returns a texture of the graphics object that can then be used to create sprites - * This can be quite useful if your geometry is complicated and needs to be reused multiple times. - * - * @param resolution {number} The resolution of the texture being generated - * @param scaleMode {number} Should be one of the scaleMode consts - * @return {Texture} a texture of the graphics object - */ -Graphics.prototype.generateTexture = function (resolution, scaleMode) -{ - resolution = resolution || 1; - - var bounds = this.getBounds(); - - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); - - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - canvasBuffer.context.scale(resolution, resolution); - - canvasBuffer.context.translate(-bounds.x,-bounds.y); - - CanvasGraphics.renderGraphics(this, canvasBuffer.context); - - return texture; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (this.isMask === true) - { - return; - } - - // this code may still be needed so leaving for now.. - // - /* - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture on the gpu too! - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.worldAlpha = this.worldAlpha; - - Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); - - return; - } - - */ - - if (this.glDirty) - { - this.dirty = true; - this.glDirty = false; - } - - renderer.setObjectRenderer(renderer.objectRenderers.graphics); - renderer.objectRenderers.graphics.render(this); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} - * @private - */ -Graphics.prototype.renderCanvas = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.alpha <= 0 || this.isMask === true) - { - return; - } - - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.alpha = this.alpha; - - Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); - - return; - } - else - { - var context = renderer.context; - var transform = this.worldTransform; - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - var resolution = renderer.resolution; - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } - } -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @return {Rectangle} the rectangular bounding area - */ -Graphics.prototype.getBounds = function (matrix) -{ - // return an empty object if the item is a mask! - if (this.isMask) - { - return math.Rectangle.EMPTY; - } - - if (this.dirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.dirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - return this._bounds; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; - - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; -}; - -/** - * Generates the cached sprite when the sprite has cacheAsBitmap = true - * - * @private - */ -Graphics.prototype._generateCachedSprite = function () -{ - var bounds = this.getLocalBounds(); - - if (!this._cachedSprite) - { - var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); - var texture = Texture.fromCanvas(canvasBuffer.canvas); - - this._cachedSprite = new Sprite(texture); - this._cachedSprite.buffer = canvasBuffer; - - this._cachedSprite.worldTransform = this.worldTransform; - } - else - { - this._cachedSprite.buffer.resize(bounds.width, bounds.height); - } - - // leverage the anchor to account for the offset of the element - this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); - this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); - - // this._cachedSprite.buffer.context.save(); - this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); - - // make sure we set the alpha of the graphics to 1 for the render.. - this.worldAlpha = 1; - - // now render the graphic.. - CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); - - this._cachedSprite.alpha = this.alpha; -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Graphics.prototype.updateCachedSpriteTexture = function () -{ - var cachedSprite = this._cachedSprite; - var texture = cachedSprite.texture; - var canvas = cachedSprite.buffer.canvas; - - texture.baseTexture.width = canvas.width; - texture.baseTexture.height = canvas.height; - texture.crop.width = texture.frame.width = canvas.width; - texture.crop.height = texture.frame.height = canvas.height; - - cachedSprite._width = canvas.width; - cachedSprite._height = canvas.height; - - // update the dirty base textures - texture.baseTexture.dirty(); -}; - -/** - * Destroys a previous cached sprite. - * - */ -Graphics.prototype.destroyCachedSprite = function () -{ - this._cachedSprite.texture.destroy(true); - - // let the gc collect the unused sprite - // TODO could be object pooled! - this._cachedSprite = null; -}; - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. - * @return {GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = this.filling; - this.currentPath = data; - } - - this.dirty = true; - - return data; -}; diff --git a/src/core/primitives/GraphicsData.js b/src/core/primitives/GraphicsData.js deleted file mode 100644 index 788f6f2..0000000 --- a/src/core/primitives/GraphicsData.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * A GraphicsData object. - * - * @class - * @namespace PIXI - */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - this.lineWidth = lineWidth; - this.lineColor = lineColor; - this.lineAlpha = lineAlpha; - this._lineTint = lineColor; - - this.fillColor = fillColor; - this.fillAlpha = fillAlpha; - this._fillTint = fillColor; - this.fill = fill; - - this.shape = shape; - this.type = shape.type; -} - -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {GraphicsData} - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js deleted file mode 100644 index 1a508f9..0000000 --- a/src/core/primitives/GraphicsRenderer.js +++ /dev/null @@ -1,874 +0,0 @@ -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; - -/** - * 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 ); - -// var matrix = graphics.worldTransform.clone(); -// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); -// matrix.append(graphics.worldTransform); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.maskManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.maskManager.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.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); - - 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/primitives/WebGLGraphicsData.js b/src/core/primitives/WebGLGraphicsData.js deleted file mode 100755 index 41be102..0000000 --- a/src/core/primitives/WebGLGraphicsData.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @class - * @private - */ -function WebGLGraphicsData(gl) { - this.gl = gl; - - //TODO does this need to be split before uploding?? - this.color = [0,0,0]; // color split! - this.points = []; - this.indices = []; - this.buffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - this.mode = 1; - this.alpha = 1; - this.dirty = true; -} - -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; -module.exports = WebGLGraphicsData; - -/** - * - */ -WebGLGraphicsData.prototype.reset = function () { - this.points = []; - this.indices = []; -}; - -/** - * - */ -WebGLGraphicsData.prototype.upload = function () { - var gl = this.gl; - -// this.lastIndex = graphics.graphicsData.length; - this.glPoints = new Float32Array(this.points); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); - - this.glIndicies = new Uint16Array(this.indices); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); - - this.dirty = false; -}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6c1fb7d..4e82baa 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -174,6 +174,9 @@ // time to create the render managers! each one focuses on managing a state in webGL + // initialize the context so it is ready for the managers. + this._initContext(); + /** * Deals with managing the shader programs and their attribs * @member {WebGLShaderManager} @@ -204,17 +207,11 @@ */ this.blendModeManager = new WebGLBlendModeManager(this); - - this.blendModes = null; this._boundUpdateTexture = this.updateTexture.bind(this); this._boundDestroyTexture = this.destroyTexture.bind(this); - - - // time init the context.. - this._initContext(); this.currentRenderTarget = this.renderTarget; @@ -228,27 +225,18 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - /** - * Manages the renderer of specific objects. - * - * @member {object} - */ - this.objectRenderers = {}; - this.currentRenderer = new ObjectRenderer(); - - // create an instance of each registered object renderer - for (var o in WebGLRenderer._objectRenderers) { - this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); - } - + this.initPlugins(); } // constructor WebGLRenderer.prototype.constructor = WebGLRenderer; module.exports = WebGLRenderer; +WebGLRenderer.glContextId = 0; + +utils.pluginTarget.mixin(WebGLRenderer); utils.eventTarget.mixin(WebGLRenderer.prototype); Object.defineProperties(WebGLRenderer.prototype, { @@ -297,11 +285,9 @@ gl.enable(gl.BLEND); this.renderTarget = new RenderTarget(this.gl, this.width, this.height, null, true); - this.emit('context', gl); - // now resize and we are good to go! this.resize(this.width, this.height); }; @@ -349,7 +335,7 @@ gl.clear(gl.COLOR_BUFFER_BIT); } - this.renderDisplayObject(object, this.renderTarget)//this.projection); + this.renderDisplayObject(object, this.renderTarget);//this.projection); }; /** @@ -536,12 +522,7 @@ this.maskManager.destroy(); this.filterManager.destroy(); - for (var o in this.objectRenderers) { - this.objectRenderers[o].destroy(); - this.objectRenderers[o] = null; - } - - this.objectRenderers = null; + this.destroyPlugins(); // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -611,11 +592,3 @@ this.blendModes[CONST.blendModes.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; } }; - -WebGLRenderer.glContextId = 0; -WebGLRenderer._objectRenderers = {}; - -WebGLRenderer.registerObjectRenderer = function (name, ctor) { - WebGLRenderer._objectRenderers[name] = ctor; -}; - diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js index 87c99f0..d6ed3c6 100644 --- a/src/core/renderers/webgl/managers/WebGLFilterManager.js +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -51,7 +51,7 @@ */ WebGLFilterManager.prototype.begin = function (buffer) { - this.defaultShader = this.renderer.shaderManager.defaultShader; + this.defaultShader = this.renderer.shaderManager.plugins.defaultShader; this.width = this.renderer.projection.x * 2; this.height = -this.renderer.projection.y * 2; @@ -385,7 +385,7 @@ if (!shader) { - shader = new Shader(gl); + shader = new Shader(this.renderer.shaderManager); shader.fragmentSrc = filter.fragmentSrc; shader.uniforms = filter.uniforms; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js index 6a1f782..d6a11c1 100644 --- a/src/core/renderers/webgl/managers/WebGLMaskManager.js +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -126,11 +126,11 @@ // bind the graphics object.. var projection = this.renderer.projection, offset = this.renderer.offset, - shader;// = this.renderer.shaderManager.primitiveShader; + shader;// = this.renderer.shaderManager.plugins.primitiveShader; if (webGLData.mode === 1) { - shader = this.renderer.shaderManager.complexPrimitiveShader; + shader = this.renderer.shaderManager.plugins.complexPrimitiveShader; this.renderer.shaderManager.setShader(shader); @@ -156,7 +156,7 @@ else { //this.renderer.shaderManager.activatePrimitiveShader(); - shader = this.renderer.shaderManager.primitiveShader; + shader = this.renderer.shaderManager.plugins.primitiveShader; this.renderer.shaderManager.setShader( shader ); gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); @@ -292,11 +292,11 @@ */ WebGLMaskManager.prototype.pushMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); if (maskData.dirty) { - this.renderer.objectRenderers.graphics.updateGraphics(maskData, this.renderer.gl); + this.renderer.plugins.graphics.updateGraphics(maskData, this.renderer.gl); } if (!maskData._webGL[this.renderer.gl.id].data.length) @@ -314,7 +314,7 @@ */ WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); this.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); }; diff --git a/src/core/renderers/webgl/managers/WebGLShaderManager.js b/src/core/renderers/webgl/managers/WebGLShaderManager.js index 90c6477..9ebb58e 100644 --- a/src/core/renderers/webgl/managers/WebGLShaderManager.js +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -1,9 +1,5 @@ var WebGLManager = require('./WebGLManager'), - PrimitiveShader = require('../shaders/PrimitiveShader'), - ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), - Shader = require('../shaders/Shader'), - FastShader = require('../shaders/FastShader'), - StripShader = require('../shaders/StripShader'); + utils = require('../../../utils'); /** * @class @@ -51,41 +47,16 @@ */ this.currentShader = null; - // this shader is used for rendering primitives - this.primitiveShader = null; - - // this shader is used for rendering triangle strips - this.complexPrimitiveShader = null; - - // this shader is used for the default sprite rendering - this.defaultShader = null; - - // this shader is used for the fast sprite rendering - this.fastShader = null; - - // the next one is used for rendering triangle strips - this.stripShader = null; + this.initPlugins(); // listen for context and update necessary shaders var self = this; - this.renderer.on('context', function (event) + this.renderer.on('context', function () { - var gl = event.data; - - // this shader is used for rendering primitives - self.primitiveShader = new PrimitiveShader(gl); - - // this shader is used for rendering triangle strips - self.complexPrimitiveShader = new ComplexPrimitiveShader(gl); - - // this shader is used for the default sprite rendering - self.defaultShader = new Shader(gl); - - // this shader is used for the fast sprite rendering - self.fastShader = new FastShader(gl); - - // the next one is used for rendering triangle strips - self.stripShader = new StripShader(gl); + for (var o in this.plugins) + { + this.plugins[o] = new (this.plugins[o].constructor)(self); + } self.setShader(self.defaultShader); }); @@ -95,6 +66,8 @@ WebGLShaderManager.prototype.constructor = WebGLShaderManager; module.exports = WebGLShaderManager; +utils.pluginTarget.mixin(WebGLShaderManager); + /** * Takes the attributes given in parameters. * @@ -164,24 +137,11 @@ */ WebGLShaderManager.prototype.destroy = function () { + this.destroyPlugins(); + this.attribState = null; this.tempAttribState = null; - this.primitiveShader.destroy(); - this.primitiveShader = null; - - this.complexPrimitiveShader.destroy(); - this.complexPrimitiveShader = null; - - this.defaultShader.destroy(); - this.defaultShader = null; - - this.fastShader.destroy(); - this.fastShader = null; - - this.stripShader.destroy(); - this.stripShader = null; - this.renderer = null; }; diff --git a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js b/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js deleted file mode 100644 index f8a07da..0000000 --- a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js +++ /dev/null @@ -1,53 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function ComplexPrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform vec3 tint;', - 'uniform float alpha;', - 'uniform vec3 color;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - color: { type: '3f', value: [0,0,0] }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); -ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; -module.exports = ComplexPrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/FastShader.js b/src/core/renderers/webgl/shaders/FastShader.js index d198f10..c80d040 100644 --- a/src/core/renderers/webgl/shaders/FastShader.js +++ b/src/core/renderers/webgl/shaders/FastShader.js @@ -1,15 +1,16 @@ -var Shader = require('./Shader'); +var Shader = require('./Shader'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @extends Shader * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. */ -function FastShader(gl) +function FastShader(shaderManager) { Shader.call(this, - gl, + shaderManager, // vertex shader [ 'attribute vec2 aVertexPosition;', @@ -70,3 +71,5 @@ FastShader.prototype = Object.create(Shader.prototype); FastShader.prototype.constructor = FastShader; module.exports = FastShader; + +WebGLShaderManager.registerPlugin('fastShader', FastShader); diff --git a/src/core/renderers/webgl/shaders/PrimitiveShader.js b/src/core/renderers/webgl/shaders/PrimitiveShader.js deleted file mode 100644 index 753c475..0000000 --- a/src/core/renderers/webgl/shaders/PrimitiveShader.js +++ /dev/null @@ -1,55 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform float alpha;', - 'uniform float flipY;', - 'uniform vec3 tint;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aColor:0 - } - ); -} - -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; -module.exports = PrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/Shader.js b/src/core/renderers/webgl/shaders/Shader.js index d065a76..c60c5f9 100644 --- a/src/core/renderers/webgl/shaders/Shader.js +++ b/src/core/renderers/webgl/shaders/Shader.js @@ -1,12 +1,16 @@ -var utils = require('../../../utils'); +var utils = require('../../../utils'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @namespace PIXI - * @param [fragmentSrc] {string} The source of the fragment shader. + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. * @param [vertexSrc] {string} The source of the vertex shader. + * @param [fragmentSrc] {string} The source of the fragment shader. + * @param customUniforms {object} Custom uniforms to use to augment the built-in ones. + * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Shader(gl, vertexSrc, fragmentSrc, customUniforms, customAttributes) +function Shader(shaderManager, vertexSrc, fragmentSrc, customUniforms, customAttributes) { /** * @member {number} @@ -18,7 +22,7 @@ * @member {WebGLContext} * @readonly */ - this.gl = gl; + this.gl = shaderManager.renderer.gl; /** * The WebGL program. @@ -31,8 +35,8 @@ uSampler: { type: 'sampler2D', value: 0 }, projectionVector: { type: '2f', value: { x: 0, y: 0 } }, offsetVector: { type: '2f', value: { x: 0, y: 0 } }, - projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, - 0, 1, 0, + projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, + 0, 1, 0, 0, 0, 1) }, dimensions: { type: '4f', value: new Float32Array(4) } }; @@ -103,6 +107,8 @@ Shader.prototype.constructor = Shader; module.exports = Shader; +WebGLShaderManager.registerPlugin('defaultShader', Shader); + Shader.prototype.init = function () { this.compile(); @@ -506,4 +512,4 @@ } return shader; -}; \ No newline at end of file +}; diff --git a/src/core/renderers/webgl/shaders/SpriteShader.js b/src/core/renderers/webgl/shaders/SpriteShader.js deleted file mode 100644 index 1805b0a..0000000 --- a/src/core/renderers/webgl/shaders/SpriteShader.js +++ /dev/null @@ -1,42 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function SpriteShader(gl) -{ - Shader.call(this, - gl, - null, - // fragment shader - [ - 'precision lowp float;', - - 'varying vec2 vTextureCoord;', - 'varying vec4 vColor;', - - 'uniform sampler2D uSampler;', - - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aTextureCoord: 0, - aColor: 0 - } - ); -} - -SpriteShader.prototype = Object.create(Shader.prototype); -SpriteShader.prototype.constructor = SpriteShader; -module.exports = SpriteShader; diff --git a/src/core/renderers/webgl/shaders/StripShader.js b/src/core/renderers/webgl/shaders/StripShader.js deleted file mode 100644 index f487381..0000000 --- a/src/core/renderers/webgl/shaders/StripShader.js +++ /dev/null @@ -1,54 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function StripShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - // 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform vec2 projectionVector;', - 'uniform vec2 offsetVector;', - - 'varying vec2 vTextureCoord;', - - 'void main(void){', - ' vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);', - ' v -= offsetVector.xyx;', - ' gl_Position = vec4( v.x / projectionVector.x -1.0, v.y / -projectionVector.y + 1.0 , 0.0, 1.0);', - ' vTextureCoord = aTextureCoord;', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'uniform float alpha;', - 'uniform sampler2D uSampler;', - - 'varying vec2 vTextureCoord;', - - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y)) * alpha;', - '}' - ].join('\n'), - // custom uniforms - { - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -StripShader.prototype = Object.create(Shader.prototype); -StripShader.prototype.constructor = StripShader; -module.exports = StripShader; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index 0a34e55..43e2d00 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -33,7 +33,7 @@ * @property texture * @type Any */ - this.texture = null + this.texture = null; this.width = 0; this.height = 0; @@ -56,11 +56,11 @@ this.frameBuffer = gl.createFramebuffer(); - /* + /* A frame buffer needs a target to render to.. create a texture and bind it attach it to the framebuffer.. */ - + this.texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, this.texture); @@ -70,13 +70,13 @@ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); 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); - + gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer ); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0); - - /* + + /* The stencil buffer is used for masking in pixi lets create one and then add attach it to the framebuffer.. */ @@ -86,7 +86,7 @@ } this.resize(width, height); - + }; RenderTarget.prototype.constructor = RenderTarget; @@ -94,21 +94,21 @@ /** * Clears the filter texture. -* +* * @method clear */ RenderTarget.prototype.clear = function() { var gl = this.gl; - + gl.clearColor(0,0,0, 0); gl.clear(gl.COLOR_BUFFER_BIT); }; RenderTarget.prototype.activate = function() { - this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer ); -} + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer); +}; /** * Resizes the texture to the specified width and height @@ -119,25 +119,25 @@ */ RenderTarget.prototype.resize = function(width, height) { - width = width | 0; - height = height | 0; - - if(this.width === width && this.height === height) return; + if (this.width === width && this.height === height) { + return; + } this.width = width; this.height = height; + this.projectionMatrix = new math.Matrix(); - - if(!this.root) + + if (!this.root) { var gl = this.gl; this.projectionMatrix.a = 1/width*2; - this.projectionMatrix.d = 1/height*2; + this.projectionMatrix.d = -1/height*2; this.projectionMatrix.tx = -1; - this.projectionMatrix.ty = -1; + this.projectionMatrix.ty = 1; gl.bindTexture(gl.TEXTURE_2D, this.texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width , height , 0, gl.RGBA, gl.UNSIGNED_BYTE, null); @@ -157,7 +157,7 @@ /** * Destroys the filter texture. -* +* * @method destroy */ RenderTarget.prototype.destroy = function() diff --git a/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js b/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js index a63f4d7..3f37b7d 100644 --- a/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js +++ b/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js @@ -183,7 +183,7 @@ */ WebGLFastSpriteBatch.prototype.begin = function (spriteBatch) { - this.shader = this.renderer.shaderManager.fastShader; + this.shader = this.renderer.shaderManager.plugins.fastShader; this.matrix = spriteBatch.worldTransform.toArray(true); diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index bf8db28..2dc47ff 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -201,8 +201,8 @@ Sprite.prototype._renderWebGL = function (renderer) { - renderer.setObjectRenderer(renderer.objectRenderers.sprite); - renderer.objectRenderers.sprite.render(this); + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); }; /** diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js new file mode 100644 index 0000000..41be102 --- /dev/null +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -0,0 +1,48 @@ +/** + * @class + * @private + */ +function WebGLGraphicsData(gl) { + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +} + +WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; +module.exports = WebGLGraphicsData; + +/** + * + */ +WebGLGraphicsData.prototype.reset = function () { + this.points = []; + this.indices = []; +}; + +/** + * + */ +WebGLGraphicsData.prototype.upload = function () { + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; diff --git a/src/core/index.js b/src/core/index.js index 6f744e9..1d0a398 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -23,12 +23,12 @@ Sprite: require('./sprites/Sprite'), SpriteBatch: require('./sprites/SpriteBatch'), - SpriteRenderer: require('./sprites/SpriteRenderer'), + SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), // primitives - Graphics: require('./primitives/Graphics'), - GraphicsData: require('./primitives/GraphicsData'), - GraphicsRenderer: require('./primitives/GraphicsRenderer'), + Graphics: require('./graphics/Graphics'), + GraphicsData: require('./graphics/GraphicsData'), + GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,10 +42,8 @@ CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), // renderers - webgl - WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), - // WebGLGraphics: require('./renderers/webgl/utils/WebGLGraphics'), - - + WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), + Shader: require('./renderers/webgl/shaders/Shader'), /** * This helper function will automatically detect which renderer you should be using. @@ -103,7 +101,3 @@ return core.autoDetectRenderer(width, height, options, isAndroid); } }; - -// Adding here for now.. sure there is a better place! -core.WebGLRenderer.registerObjectRenderer('sprite', core.SpriteRenderer); -core.WebGLRenderer.registerObjectRenderer('graphics', core.GraphicsRenderer); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index a086950..81de05c 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -268,6 +268,6 @@ matrix.ty = this.ty; return matrix; -} +}; Matrix.IDENTITY = new Matrix(); diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js deleted file mode 100644 index 8db9b45..0000000 --- a/src/core/primitives/Graphics.js +++ /dev/null @@ -1,1116 +0,0 @@ -var DisplayObjectContainer = require('../display/DisplayObjectContainer'), - //Sprite = require('../display/Sprite'), - Texture = require('../textures/Texture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), - // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), - GraphicsData = require('./GraphicsData'), - math = require('../math'), - CONST = require('../const'); - -/** - * The Graphics class contains methods used to draw primitive shapes such as lines, circles and - * rectangles to the display, and color and fill them. - * - * @class - * @extends DisplayObjectContainer - * @namespace PIXI - */ -function Graphics() -{ - DisplayObjectContainer.call(this); - - this.renderable = true; - - /** - * The alpha value used when filling the Graphics object. - * - * @member {number} - * @default 1 - */ - this.fillAlpha = 1; - - /** - * The width (thickness) of any lines drawn. - * - * @member {number} - * @default 0 - */ - this.lineWidth = 0; - - /** - * The color of any lines drawn. - * - * @member {string} - * @default 0 - */ - this.lineColor = 0; - - /** - * Graphics data - * - * @member {GraphicsData[]} - * @private - */ - this.graphicsData = []; - - /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * Current path - * - * @member {GraphicsData} - * @private - */ - this.currentPath = null; - - /** - * Array containing some WebGL-related properties used by the WebGL renderer. - * - * @member {object} - * @private - */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; - - /** - * Whether this shape is being used as a mask. - * - * @member {boolean} - */ - this.isMask = false; - - /** - * The bounds' padding used for bounds calculation. - * - * @member {number} - */ - this.boundsPadding = 0; - - /** - * A cache of the local bounds to prevent recalculation. - * - * @member {Rectangle} - * @private - */ - this._localBounds = new math.Rectangle(0,0,1,1); - - /** - * Used to detect if the graphics object has changed. If this is set to true then the graphics - * object will be recalculated. - * - * @member {boolean} - * @private - */ - this.dirty = true; - - /** - * Used to detect if the WebGL graphics object has changed. If this is set to true then the - * graphics object will be recalculated. - * - * @member {boolean} - * @private - */ - this.glDirty = false; - - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; -} - -// constructor -Graphics.prototype = Object.create(DisplayObjectContainer.prototype); -Graphics.prototype.constructor = Graphics; -module.exports = Graphics; - -Object.defineProperties(Graphics.prototype, { - /** - * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. - * This is useful if your graphics element does not change often, as it will speed up the rendering - * of the object in exchange for taking up texture memory. It is also useful if you need the graphics - * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if - * you are constantly redrawing the graphics element. - * - * @member {boolean} - * @memberof Graphics# - * @default false - * @private - */ - cacheAsBitmap: { - get: function () - { - return this._cacheAsBitmap; - }, - set: function (value) - { - this._cacheAsBitmap = value; - - if (this._cacheAsBitmap) - { - this._generateCachedSprite(); - } - else - { - this.destroyCachedSprite(); - this.dirty = true; - } - } - } -}); - -/** - * Creates a new Graphics object with the same values as this one. - * - * @return {Graphics} - */ -GraphicsData.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = this.dirty; - clone.glDirty = this.glDirty; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData.clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {Graphics} - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (arguments.length < 3) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); - } - else - { - // otherwise its empty so lets just set the line properties - this.currentPath.lineWidth = this.lineWidth; - this.currentPath.lineColor = this.lineColor; - this.currentPath.lineAlpha = this.lineAlpha; - } - } - - return this; -}; - -/** - * Moves the current drawing position to x, y. - * - * @param x {number} the X coordinate to move to - * @param y {number} the Y coordinate to move to - * @return {Graphics} - */ -Graphics.prototype.moveTo = function (x, y) -{ - this.drawShape(new math.Polygon([x,y])); - - return this; -}; - -/** - * Draws a line using the current line style from the current drawing position to (x, y); - * The current drawing position is then set to (x, y). - * - * @param x {number} the X coordinate to draw to - * @param y {number} the Y coordinate to draw to - * @return {Graphics} - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a quadratic bezier curve and then draws it. - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @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 {Graphics} - */ -Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var xa, - ya, - n = 20, - points = this.currentPath.shape.points; - - if (points.length === 0) - { - this.moveTo(0, 0); - } - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - for (var i = 1; i <= n; ++i) - { - j = i / n; - - xa = fromX + ( (cpX - fromX) * j ); - ya = fromY + ( (cpY - fromY) * j ); - - points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), - ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); - } - - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a bezier curve and then draws it. - * - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param cpX2 {number} Second Control point x - * @param cpY2 {number} Second Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {Graphics} - */ -Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var n = 20, - dt, - dt2, - dt3, - t2, - t3, - points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - - for (var i = 1; i <= n; ++i) - { - j = i / n; - - dt = (1 - j); - dt2 = dt * dt; - dt3 = dt2 * dt; - - t2 = j * j; - t3 = t2 * j; - - points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, - dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); - } - - this.dirty = true; - - return this; -}; - -/** - * The arcTo() method creates an arc/curve between two tangents on the canvas. - * - * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! - * - * @param x1 {number} The x-coordinate of the beginning of the arc - * @param y1 {number} The y-coordinate of the beginning of the arc - * @param x2 {number} The x-coordinate of the end of the arc - * @param y2 {number} The y-coordinate of the end of the arc - * @param radius {number} The radius of the arc - * @return {Graphics} - */ -Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points.push(x1, y1); - } - } - else - { - this.moveTo(x1, y1); - } - - var points = this.currentPath.shape.points, - fromX = points[points.length-2], - fromY = points[points.length-1], - a1 = fromY - y1, - b1 = fromX - x1, - a2 = y2 - y1, - b2 = x2 - x1, - mm = Math.abs(a1 * b2 - b1 * a2); - - if (mm < 1.0e-8 || radius === 0) - { - if (points[points.length-2] !== x1 || points[points.length-1] !== y1) - { - points.push(x1, y1); - } - } - else - { - var dd = a1 * a1 + b1 * b1, - cc = a2 * a2 + b2 * b2, - tt = a1 * a2 + b1 * b2, - k1 = radius * Math.sqrt(dd) / mm, - k2 = radius * Math.sqrt(cc) / mm, - j1 = k1 * tt / dd, - j2 = k2 * tt / cc, - cx = k1 * b2 + k2 * b1, - cy = k1 * a2 + k2 * a1, - px = b1 * (k2 + j1), - py = a1 * (k2 + j1), - qx = b2 * (k1 + j2), - qy = a2 * (k1 + j2), - startAngle = Math.atan2(py - cy, px - cx), - endAngle = Math.atan2(qy - cy, qx - cx); - - this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); - } - - this.dirty = true; - - return this; -}; - -/** - * The arc method creates an arc/curve (used to create circles, or parts of circles). - * - * @param cx {number} The x-coordinate of the center of the circle - * @param cy {number} The y-coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) - * @param endAngle {number} The ending angle, in radians - * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {Graphics} - */ -Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - var points; - - // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... - if (this.currentPath) - { - points = this.currentPath.shape.points; - - if (points.length === 0) - { - points.push(startX, startY); - } - else if (points[points.length-2] !== startX || points[points.length-1] !== startY) - { - points.push(startX, startY); - } - } - else - { - this.moveTo(startX, startY); - points = this.currentPath.shape.points; - } - - if (startAngle === endAngle) - { - return this; - } - - if (!anticlockwise && endAngle <= startAngle) - { - endAngle += Math.PI * 2; - } - else if (anticlockwise && startAngle <= endAngle) - { - startAngle += Math.PI * 2; - } - - var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); - var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; - - if (sweep === 0) - { - return this; - } - - var theta = sweep/(segs*2); - var theta2 = theta*2; - - var cTheta = Math.cos(theta); - var sTheta = Math.sin(theta); - - var segMinus = segs - 1; - - var remainder = ( segMinus % 1 ) / segMinus; - - for (var i = 0; i <= segMinus; ++i) - { - var real = i + remainder * i; - var angle = ((theta) + startAngle + (theta2 * real)); - - var c = Math.cos(angle); - var s = -Math.sin(angle); - - points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, - ( (cTheta * -s) + (sTheta * c) ) * radius + cy); - } - - this.dirty = true; - - return this; -}; - -/** - * Specifies a simple one-color fill that subsequent calls to other Graphics methods - * (such as lineTo() or drawCircle()) use when drawing. - * - * @param color {number} the color of the fill - * @param alpha {number} the alpha of the fill - * @return {Graphics} - */ -Graphics.prototype.beginFill = function (color, alpha) -{ - this.filling = true; - this.fillColor = color || 0; - this.fillAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length <= 2) - { - this.currentPath.fill = this.filling; - this.currentPath.fillColor = this.fillColor; - this.currentPath.fillAlpha = this.fillAlpha; - } - } - return this; -}; - -/** - * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. - * - * @return {Graphics} - */ -Graphics.prototype.endFill = function () -{ - this.filling = false; - this.fillColor = null; - this.fillAlpha = 1; - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @return {Graphics} - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {Graphics} - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {Graphics} - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {Array} The path data used to construct the polygon. - * @return {Graphics} - */ -Graphics.prototype.drawPolygon = function (path) -{ - if (!(path instanceof Array)) - { - path = Array.prototype.slice.call(arguments); - } - - this.drawShape(new math.Polygon(path)); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {Graphics} - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty = true; - this.clearDirty = true; - this.graphicsData = []; - - return this; -}; - -/** - * Useful function that returns a texture of the graphics object that can then be used to create sprites - * This can be quite useful if your geometry is complicated and needs to be reused multiple times. - * - * @param resolution {number} The resolution of the texture being generated - * @param scaleMode {number} Should be one of the scaleMode consts - * @return {Texture} a texture of the graphics object - */ -Graphics.prototype.generateTexture = function (resolution, scaleMode) -{ - resolution = resolution || 1; - - var bounds = this.getBounds(); - - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); - - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - canvasBuffer.context.scale(resolution, resolution); - - canvasBuffer.context.translate(-bounds.x,-bounds.y); - - CanvasGraphics.renderGraphics(this, canvasBuffer.context); - - return texture; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (this.isMask === true) - { - return; - } - - // this code may still be needed so leaving for now.. - // - /* - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture on the gpu too! - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.worldAlpha = this.worldAlpha; - - Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); - - return; - } - - */ - - if (this.glDirty) - { - this.dirty = true; - this.glDirty = false; - } - - renderer.setObjectRenderer(renderer.objectRenderers.graphics); - renderer.objectRenderers.graphics.render(this); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} - * @private - */ -Graphics.prototype.renderCanvas = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.alpha <= 0 || this.isMask === true) - { - return; - } - - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.alpha = this.alpha; - - Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); - - return; - } - else - { - var context = renderer.context; - var transform = this.worldTransform; - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - var resolution = renderer.resolution; - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } - } -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @return {Rectangle} the rectangular bounding area - */ -Graphics.prototype.getBounds = function (matrix) -{ - // return an empty object if the item is a mask! - if (this.isMask) - { - return math.Rectangle.EMPTY; - } - - if (this.dirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.dirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - return this._bounds; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; - - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; -}; - -/** - * Generates the cached sprite when the sprite has cacheAsBitmap = true - * - * @private - */ -Graphics.prototype._generateCachedSprite = function () -{ - var bounds = this.getLocalBounds(); - - if (!this._cachedSprite) - { - var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); - var texture = Texture.fromCanvas(canvasBuffer.canvas); - - this._cachedSprite = new Sprite(texture); - this._cachedSprite.buffer = canvasBuffer; - - this._cachedSprite.worldTransform = this.worldTransform; - } - else - { - this._cachedSprite.buffer.resize(bounds.width, bounds.height); - } - - // leverage the anchor to account for the offset of the element - this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); - this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); - - // this._cachedSprite.buffer.context.save(); - this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); - - // make sure we set the alpha of the graphics to 1 for the render.. - this.worldAlpha = 1; - - // now render the graphic.. - CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); - - this._cachedSprite.alpha = this.alpha; -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Graphics.prototype.updateCachedSpriteTexture = function () -{ - var cachedSprite = this._cachedSprite; - var texture = cachedSprite.texture; - var canvas = cachedSprite.buffer.canvas; - - texture.baseTexture.width = canvas.width; - texture.baseTexture.height = canvas.height; - texture.crop.width = texture.frame.width = canvas.width; - texture.crop.height = texture.frame.height = canvas.height; - - cachedSprite._width = canvas.width; - cachedSprite._height = canvas.height; - - // update the dirty base textures - texture.baseTexture.dirty(); -}; - -/** - * Destroys a previous cached sprite. - * - */ -Graphics.prototype.destroyCachedSprite = function () -{ - this._cachedSprite.texture.destroy(true); - - // let the gc collect the unused sprite - // TODO could be object pooled! - this._cachedSprite = null; -}; - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. - * @return {GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = this.filling; - this.currentPath = data; - } - - this.dirty = true; - - return data; -}; diff --git a/src/core/primitives/GraphicsData.js b/src/core/primitives/GraphicsData.js deleted file mode 100644 index 788f6f2..0000000 --- a/src/core/primitives/GraphicsData.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * A GraphicsData object. - * - * @class - * @namespace PIXI - */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - this.lineWidth = lineWidth; - this.lineColor = lineColor; - this.lineAlpha = lineAlpha; - this._lineTint = lineColor; - - this.fillColor = fillColor; - this.fillAlpha = fillAlpha; - this._fillTint = fillColor; - this.fill = fill; - - this.shape = shape; - this.type = shape.type; -} - -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {GraphicsData} - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js deleted file mode 100644 index 1a508f9..0000000 --- a/src/core/primitives/GraphicsRenderer.js +++ /dev/null @@ -1,874 +0,0 @@ -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; - -/** - * 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 ); - -// var matrix = graphics.worldTransform.clone(); -// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); -// matrix.append(graphics.worldTransform); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.maskManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.maskManager.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.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); - - 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/primitives/WebGLGraphicsData.js b/src/core/primitives/WebGLGraphicsData.js deleted file mode 100755 index 41be102..0000000 --- a/src/core/primitives/WebGLGraphicsData.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @class - * @private - */ -function WebGLGraphicsData(gl) { - this.gl = gl; - - //TODO does this need to be split before uploding?? - this.color = [0,0,0]; // color split! - this.points = []; - this.indices = []; - this.buffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - this.mode = 1; - this.alpha = 1; - this.dirty = true; -} - -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; -module.exports = WebGLGraphicsData; - -/** - * - */ -WebGLGraphicsData.prototype.reset = function () { - this.points = []; - this.indices = []; -}; - -/** - * - */ -WebGLGraphicsData.prototype.upload = function () { - var gl = this.gl; - -// this.lastIndex = graphics.graphicsData.length; - this.glPoints = new Float32Array(this.points); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); - - this.glIndicies = new Uint16Array(this.indices); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); - - this.dirty = false; -}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6c1fb7d..4e82baa 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -174,6 +174,9 @@ // time to create the render managers! each one focuses on managing a state in webGL + // initialize the context so it is ready for the managers. + this._initContext(); + /** * Deals with managing the shader programs and their attribs * @member {WebGLShaderManager} @@ -204,17 +207,11 @@ */ this.blendModeManager = new WebGLBlendModeManager(this); - - this.blendModes = null; this._boundUpdateTexture = this.updateTexture.bind(this); this._boundDestroyTexture = this.destroyTexture.bind(this); - - - // time init the context.. - this._initContext(); this.currentRenderTarget = this.renderTarget; @@ -228,27 +225,18 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - /** - * Manages the renderer of specific objects. - * - * @member {object} - */ - this.objectRenderers = {}; - this.currentRenderer = new ObjectRenderer(); - - // create an instance of each registered object renderer - for (var o in WebGLRenderer._objectRenderers) { - this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); - } - + this.initPlugins(); } // constructor WebGLRenderer.prototype.constructor = WebGLRenderer; module.exports = WebGLRenderer; +WebGLRenderer.glContextId = 0; + +utils.pluginTarget.mixin(WebGLRenderer); utils.eventTarget.mixin(WebGLRenderer.prototype); Object.defineProperties(WebGLRenderer.prototype, { @@ -297,11 +285,9 @@ gl.enable(gl.BLEND); this.renderTarget = new RenderTarget(this.gl, this.width, this.height, null, true); - this.emit('context', gl); - // now resize and we are good to go! this.resize(this.width, this.height); }; @@ -349,7 +335,7 @@ gl.clear(gl.COLOR_BUFFER_BIT); } - this.renderDisplayObject(object, this.renderTarget)//this.projection); + this.renderDisplayObject(object, this.renderTarget);//this.projection); }; /** @@ -536,12 +522,7 @@ this.maskManager.destroy(); this.filterManager.destroy(); - for (var o in this.objectRenderers) { - this.objectRenderers[o].destroy(); - this.objectRenderers[o] = null; - } - - this.objectRenderers = null; + this.destroyPlugins(); // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -611,11 +592,3 @@ this.blendModes[CONST.blendModes.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; } }; - -WebGLRenderer.glContextId = 0; -WebGLRenderer._objectRenderers = {}; - -WebGLRenderer.registerObjectRenderer = function (name, ctor) { - WebGLRenderer._objectRenderers[name] = ctor; -}; - diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js index 87c99f0..d6ed3c6 100644 --- a/src/core/renderers/webgl/managers/WebGLFilterManager.js +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -51,7 +51,7 @@ */ WebGLFilterManager.prototype.begin = function (buffer) { - this.defaultShader = this.renderer.shaderManager.defaultShader; + this.defaultShader = this.renderer.shaderManager.plugins.defaultShader; this.width = this.renderer.projection.x * 2; this.height = -this.renderer.projection.y * 2; @@ -385,7 +385,7 @@ if (!shader) { - shader = new Shader(gl); + shader = new Shader(this.renderer.shaderManager); shader.fragmentSrc = filter.fragmentSrc; shader.uniforms = filter.uniforms; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js index 6a1f782..d6a11c1 100644 --- a/src/core/renderers/webgl/managers/WebGLMaskManager.js +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -126,11 +126,11 @@ // bind the graphics object.. var projection = this.renderer.projection, offset = this.renderer.offset, - shader;// = this.renderer.shaderManager.primitiveShader; + shader;// = this.renderer.shaderManager.plugins.primitiveShader; if (webGLData.mode === 1) { - shader = this.renderer.shaderManager.complexPrimitiveShader; + shader = this.renderer.shaderManager.plugins.complexPrimitiveShader; this.renderer.shaderManager.setShader(shader); @@ -156,7 +156,7 @@ else { //this.renderer.shaderManager.activatePrimitiveShader(); - shader = this.renderer.shaderManager.primitiveShader; + shader = this.renderer.shaderManager.plugins.primitiveShader; this.renderer.shaderManager.setShader( shader ); gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); @@ -292,11 +292,11 @@ */ WebGLMaskManager.prototype.pushMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); if (maskData.dirty) { - this.renderer.objectRenderers.graphics.updateGraphics(maskData, this.renderer.gl); + this.renderer.plugins.graphics.updateGraphics(maskData, this.renderer.gl); } if (!maskData._webGL[this.renderer.gl.id].data.length) @@ -314,7 +314,7 @@ */ WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); this.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); }; diff --git a/src/core/renderers/webgl/managers/WebGLShaderManager.js b/src/core/renderers/webgl/managers/WebGLShaderManager.js index 90c6477..9ebb58e 100644 --- a/src/core/renderers/webgl/managers/WebGLShaderManager.js +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -1,9 +1,5 @@ var WebGLManager = require('./WebGLManager'), - PrimitiveShader = require('../shaders/PrimitiveShader'), - ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), - Shader = require('../shaders/Shader'), - FastShader = require('../shaders/FastShader'), - StripShader = require('../shaders/StripShader'); + utils = require('../../../utils'); /** * @class @@ -51,41 +47,16 @@ */ this.currentShader = null; - // this shader is used for rendering primitives - this.primitiveShader = null; - - // this shader is used for rendering triangle strips - this.complexPrimitiveShader = null; - - // this shader is used for the default sprite rendering - this.defaultShader = null; - - // this shader is used for the fast sprite rendering - this.fastShader = null; - - // the next one is used for rendering triangle strips - this.stripShader = null; + this.initPlugins(); // listen for context and update necessary shaders var self = this; - this.renderer.on('context', function (event) + this.renderer.on('context', function () { - var gl = event.data; - - // this shader is used for rendering primitives - self.primitiveShader = new PrimitiveShader(gl); - - // this shader is used for rendering triangle strips - self.complexPrimitiveShader = new ComplexPrimitiveShader(gl); - - // this shader is used for the default sprite rendering - self.defaultShader = new Shader(gl); - - // this shader is used for the fast sprite rendering - self.fastShader = new FastShader(gl); - - // the next one is used for rendering triangle strips - self.stripShader = new StripShader(gl); + for (var o in this.plugins) + { + this.plugins[o] = new (this.plugins[o].constructor)(self); + } self.setShader(self.defaultShader); }); @@ -95,6 +66,8 @@ WebGLShaderManager.prototype.constructor = WebGLShaderManager; module.exports = WebGLShaderManager; +utils.pluginTarget.mixin(WebGLShaderManager); + /** * Takes the attributes given in parameters. * @@ -164,24 +137,11 @@ */ WebGLShaderManager.prototype.destroy = function () { + this.destroyPlugins(); + this.attribState = null; this.tempAttribState = null; - this.primitiveShader.destroy(); - this.primitiveShader = null; - - this.complexPrimitiveShader.destroy(); - this.complexPrimitiveShader = null; - - this.defaultShader.destroy(); - this.defaultShader = null; - - this.fastShader.destroy(); - this.fastShader = null; - - this.stripShader.destroy(); - this.stripShader = null; - this.renderer = null; }; diff --git a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js b/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js deleted file mode 100644 index f8a07da..0000000 --- a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js +++ /dev/null @@ -1,53 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function ComplexPrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform vec3 tint;', - 'uniform float alpha;', - 'uniform vec3 color;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - color: { type: '3f', value: [0,0,0] }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); -ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; -module.exports = ComplexPrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/FastShader.js b/src/core/renderers/webgl/shaders/FastShader.js index d198f10..c80d040 100644 --- a/src/core/renderers/webgl/shaders/FastShader.js +++ b/src/core/renderers/webgl/shaders/FastShader.js @@ -1,15 +1,16 @@ -var Shader = require('./Shader'); +var Shader = require('./Shader'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @extends Shader * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. */ -function FastShader(gl) +function FastShader(shaderManager) { Shader.call(this, - gl, + shaderManager, // vertex shader [ 'attribute vec2 aVertexPosition;', @@ -70,3 +71,5 @@ FastShader.prototype = Object.create(Shader.prototype); FastShader.prototype.constructor = FastShader; module.exports = FastShader; + +WebGLShaderManager.registerPlugin('fastShader', FastShader); diff --git a/src/core/renderers/webgl/shaders/PrimitiveShader.js b/src/core/renderers/webgl/shaders/PrimitiveShader.js deleted file mode 100644 index 753c475..0000000 --- a/src/core/renderers/webgl/shaders/PrimitiveShader.js +++ /dev/null @@ -1,55 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform float alpha;', - 'uniform float flipY;', - 'uniform vec3 tint;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aColor:0 - } - ); -} - -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; -module.exports = PrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/Shader.js b/src/core/renderers/webgl/shaders/Shader.js index d065a76..c60c5f9 100644 --- a/src/core/renderers/webgl/shaders/Shader.js +++ b/src/core/renderers/webgl/shaders/Shader.js @@ -1,12 +1,16 @@ -var utils = require('../../../utils'); +var utils = require('../../../utils'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @namespace PIXI - * @param [fragmentSrc] {string} The source of the fragment shader. + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. * @param [vertexSrc] {string} The source of the vertex shader. + * @param [fragmentSrc] {string} The source of the fragment shader. + * @param customUniforms {object} Custom uniforms to use to augment the built-in ones. + * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Shader(gl, vertexSrc, fragmentSrc, customUniforms, customAttributes) +function Shader(shaderManager, vertexSrc, fragmentSrc, customUniforms, customAttributes) { /** * @member {number} @@ -18,7 +22,7 @@ * @member {WebGLContext} * @readonly */ - this.gl = gl; + this.gl = shaderManager.renderer.gl; /** * The WebGL program. @@ -31,8 +35,8 @@ uSampler: { type: 'sampler2D', value: 0 }, projectionVector: { type: '2f', value: { x: 0, y: 0 } }, offsetVector: { type: '2f', value: { x: 0, y: 0 } }, - projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, - 0, 1, 0, + projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, + 0, 1, 0, 0, 0, 1) }, dimensions: { type: '4f', value: new Float32Array(4) } }; @@ -103,6 +107,8 @@ Shader.prototype.constructor = Shader; module.exports = Shader; +WebGLShaderManager.registerPlugin('defaultShader', Shader); + Shader.prototype.init = function () { this.compile(); @@ -506,4 +512,4 @@ } return shader; -}; \ No newline at end of file +}; diff --git a/src/core/renderers/webgl/shaders/SpriteShader.js b/src/core/renderers/webgl/shaders/SpriteShader.js deleted file mode 100644 index 1805b0a..0000000 --- a/src/core/renderers/webgl/shaders/SpriteShader.js +++ /dev/null @@ -1,42 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function SpriteShader(gl) -{ - Shader.call(this, - gl, - null, - // fragment shader - [ - 'precision lowp float;', - - 'varying vec2 vTextureCoord;', - 'varying vec4 vColor;', - - 'uniform sampler2D uSampler;', - - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aTextureCoord: 0, - aColor: 0 - } - ); -} - -SpriteShader.prototype = Object.create(Shader.prototype); -SpriteShader.prototype.constructor = SpriteShader; -module.exports = SpriteShader; diff --git a/src/core/renderers/webgl/shaders/StripShader.js b/src/core/renderers/webgl/shaders/StripShader.js deleted file mode 100644 index f487381..0000000 --- a/src/core/renderers/webgl/shaders/StripShader.js +++ /dev/null @@ -1,54 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function StripShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - // 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform vec2 projectionVector;', - 'uniform vec2 offsetVector;', - - 'varying vec2 vTextureCoord;', - - 'void main(void){', - ' vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);', - ' v -= offsetVector.xyx;', - ' gl_Position = vec4( v.x / projectionVector.x -1.0, v.y / -projectionVector.y + 1.0 , 0.0, 1.0);', - ' vTextureCoord = aTextureCoord;', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'uniform float alpha;', - 'uniform sampler2D uSampler;', - - 'varying vec2 vTextureCoord;', - - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y)) * alpha;', - '}' - ].join('\n'), - // custom uniforms - { - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -StripShader.prototype = Object.create(Shader.prototype); -StripShader.prototype.constructor = StripShader; -module.exports = StripShader; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index 0a34e55..43e2d00 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -33,7 +33,7 @@ * @property texture * @type Any */ - this.texture = null + this.texture = null; this.width = 0; this.height = 0; @@ -56,11 +56,11 @@ this.frameBuffer = gl.createFramebuffer(); - /* + /* A frame buffer needs a target to render to.. create a texture and bind it attach it to the framebuffer.. */ - + this.texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, this.texture); @@ -70,13 +70,13 @@ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); 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); - + gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer ); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0); - - /* + + /* The stencil buffer is used for masking in pixi lets create one and then add attach it to the framebuffer.. */ @@ -86,7 +86,7 @@ } this.resize(width, height); - + }; RenderTarget.prototype.constructor = RenderTarget; @@ -94,21 +94,21 @@ /** * Clears the filter texture. -* +* * @method clear */ RenderTarget.prototype.clear = function() { var gl = this.gl; - + gl.clearColor(0,0,0, 0); gl.clear(gl.COLOR_BUFFER_BIT); }; RenderTarget.prototype.activate = function() { - this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer ); -} + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer); +}; /** * Resizes the texture to the specified width and height @@ -119,25 +119,25 @@ */ RenderTarget.prototype.resize = function(width, height) { - width = width | 0; - height = height | 0; - - if(this.width === width && this.height === height) return; + if (this.width === width && this.height === height) { + return; + } this.width = width; this.height = height; + this.projectionMatrix = new math.Matrix(); - - if(!this.root) + + if (!this.root) { var gl = this.gl; this.projectionMatrix.a = 1/width*2; - this.projectionMatrix.d = 1/height*2; + this.projectionMatrix.d = -1/height*2; this.projectionMatrix.tx = -1; - this.projectionMatrix.ty = -1; + this.projectionMatrix.ty = 1; gl.bindTexture(gl.TEXTURE_2D, this.texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width , height , 0, gl.RGBA, gl.UNSIGNED_BYTE, null); @@ -157,7 +157,7 @@ /** * Destroys the filter texture. -* +* * @method destroy */ RenderTarget.prototype.destroy = function() diff --git a/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js b/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js index a63f4d7..3f37b7d 100644 --- a/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js +++ b/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js @@ -183,7 +183,7 @@ */ WebGLFastSpriteBatch.prototype.begin = function (spriteBatch) { - this.shader = this.renderer.shaderManager.fastShader; + this.shader = this.renderer.shaderManager.plugins.fastShader; this.matrix = spriteBatch.worldTransform.toArray(true); diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index bf8db28..2dc47ff 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -201,8 +201,8 @@ Sprite.prototype._renderWebGL = function (renderer) { - renderer.setObjectRenderer(renderer.objectRenderers.sprite); - renderer.objectRenderers.sprite.render(this); + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); }; /** diff --git a/src/core/sprites/SpriteBatch.js b/src/core/sprites/SpriteBatch.js index 63cebc0..4fe33e4 100644 --- a/src/core/sprites/SpriteBatch.js +++ b/src/core/sprites/SpriteBatch.js @@ -61,7 +61,7 @@ renderer.spriteBatch.stop(); - renderer.shaderManager.setShader(renderer.shaderManager.fastShader); + renderer.shaderManager.setShader(renderer.shaderManager.plugins.fastShader); renderer.fastSpriteBatch.begin(this); renderer.fastSpriteBatch.render(this); diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js new file mode 100644 index 0000000..41be102 --- /dev/null +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -0,0 +1,48 @@ +/** + * @class + * @private + */ +function WebGLGraphicsData(gl) { + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +} + +WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; +module.exports = WebGLGraphicsData; + +/** + * + */ +WebGLGraphicsData.prototype.reset = function () { + this.points = []; + this.indices = []; +}; + +/** + * + */ +WebGLGraphicsData.prototype.upload = function () { + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; diff --git a/src/core/index.js b/src/core/index.js index 6f744e9..1d0a398 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -23,12 +23,12 @@ Sprite: require('./sprites/Sprite'), SpriteBatch: require('./sprites/SpriteBatch'), - SpriteRenderer: require('./sprites/SpriteRenderer'), + SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), // primitives - Graphics: require('./primitives/Graphics'), - GraphicsData: require('./primitives/GraphicsData'), - GraphicsRenderer: require('./primitives/GraphicsRenderer'), + Graphics: require('./graphics/Graphics'), + GraphicsData: require('./graphics/GraphicsData'), + GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,10 +42,8 @@ CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), // renderers - webgl - WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), - // WebGLGraphics: require('./renderers/webgl/utils/WebGLGraphics'), - - + WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), + Shader: require('./renderers/webgl/shaders/Shader'), /** * This helper function will automatically detect which renderer you should be using. @@ -103,7 +101,3 @@ return core.autoDetectRenderer(width, height, options, isAndroid); } }; - -// Adding here for now.. sure there is a better place! -core.WebGLRenderer.registerObjectRenderer('sprite', core.SpriteRenderer); -core.WebGLRenderer.registerObjectRenderer('graphics', core.GraphicsRenderer); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index a086950..81de05c 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -268,6 +268,6 @@ matrix.ty = this.ty; return matrix; -} +}; Matrix.IDENTITY = new Matrix(); diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js deleted file mode 100644 index 8db9b45..0000000 --- a/src/core/primitives/Graphics.js +++ /dev/null @@ -1,1116 +0,0 @@ -var DisplayObjectContainer = require('../display/DisplayObjectContainer'), - //Sprite = require('../display/Sprite'), - Texture = require('../textures/Texture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), - // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), - GraphicsData = require('./GraphicsData'), - math = require('../math'), - CONST = require('../const'); - -/** - * The Graphics class contains methods used to draw primitive shapes such as lines, circles and - * rectangles to the display, and color and fill them. - * - * @class - * @extends DisplayObjectContainer - * @namespace PIXI - */ -function Graphics() -{ - DisplayObjectContainer.call(this); - - this.renderable = true; - - /** - * The alpha value used when filling the Graphics object. - * - * @member {number} - * @default 1 - */ - this.fillAlpha = 1; - - /** - * The width (thickness) of any lines drawn. - * - * @member {number} - * @default 0 - */ - this.lineWidth = 0; - - /** - * The color of any lines drawn. - * - * @member {string} - * @default 0 - */ - this.lineColor = 0; - - /** - * Graphics data - * - * @member {GraphicsData[]} - * @private - */ - this.graphicsData = []; - - /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * Current path - * - * @member {GraphicsData} - * @private - */ - this.currentPath = null; - - /** - * Array containing some WebGL-related properties used by the WebGL renderer. - * - * @member {object} - * @private - */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; - - /** - * Whether this shape is being used as a mask. - * - * @member {boolean} - */ - this.isMask = false; - - /** - * The bounds' padding used for bounds calculation. - * - * @member {number} - */ - this.boundsPadding = 0; - - /** - * A cache of the local bounds to prevent recalculation. - * - * @member {Rectangle} - * @private - */ - this._localBounds = new math.Rectangle(0,0,1,1); - - /** - * Used to detect if the graphics object has changed. If this is set to true then the graphics - * object will be recalculated. - * - * @member {boolean} - * @private - */ - this.dirty = true; - - /** - * Used to detect if the WebGL graphics object has changed. If this is set to true then the - * graphics object will be recalculated. - * - * @member {boolean} - * @private - */ - this.glDirty = false; - - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; -} - -// constructor -Graphics.prototype = Object.create(DisplayObjectContainer.prototype); -Graphics.prototype.constructor = Graphics; -module.exports = Graphics; - -Object.defineProperties(Graphics.prototype, { - /** - * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. - * This is useful if your graphics element does not change often, as it will speed up the rendering - * of the object in exchange for taking up texture memory. It is also useful if you need the graphics - * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if - * you are constantly redrawing the graphics element. - * - * @member {boolean} - * @memberof Graphics# - * @default false - * @private - */ - cacheAsBitmap: { - get: function () - { - return this._cacheAsBitmap; - }, - set: function (value) - { - this._cacheAsBitmap = value; - - if (this._cacheAsBitmap) - { - this._generateCachedSprite(); - } - else - { - this.destroyCachedSprite(); - this.dirty = true; - } - } - } -}); - -/** - * Creates a new Graphics object with the same values as this one. - * - * @return {Graphics} - */ -GraphicsData.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = this.dirty; - clone.glDirty = this.glDirty; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData.clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {Graphics} - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (arguments.length < 3) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); - } - else - { - // otherwise its empty so lets just set the line properties - this.currentPath.lineWidth = this.lineWidth; - this.currentPath.lineColor = this.lineColor; - this.currentPath.lineAlpha = this.lineAlpha; - } - } - - return this; -}; - -/** - * Moves the current drawing position to x, y. - * - * @param x {number} the X coordinate to move to - * @param y {number} the Y coordinate to move to - * @return {Graphics} - */ -Graphics.prototype.moveTo = function (x, y) -{ - this.drawShape(new math.Polygon([x,y])); - - return this; -}; - -/** - * Draws a line using the current line style from the current drawing position to (x, y); - * The current drawing position is then set to (x, y). - * - * @param x {number} the X coordinate to draw to - * @param y {number} the Y coordinate to draw to - * @return {Graphics} - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a quadratic bezier curve and then draws it. - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @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 {Graphics} - */ -Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var xa, - ya, - n = 20, - points = this.currentPath.shape.points; - - if (points.length === 0) - { - this.moveTo(0, 0); - } - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - for (var i = 1; i <= n; ++i) - { - j = i / n; - - xa = fromX + ( (cpX - fromX) * j ); - ya = fromY + ( (cpY - fromY) * j ); - - points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), - ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); - } - - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a bezier curve and then draws it. - * - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param cpX2 {number} Second Control point x - * @param cpY2 {number} Second Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {Graphics} - */ -Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var n = 20, - dt, - dt2, - dt3, - t2, - t3, - points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - - for (var i = 1; i <= n; ++i) - { - j = i / n; - - dt = (1 - j); - dt2 = dt * dt; - dt3 = dt2 * dt; - - t2 = j * j; - t3 = t2 * j; - - points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, - dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); - } - - this.dirty = true; - - return this; -}; - -/** - * The arcTo() method creates an arc/curve between two tangents on the canvas. - * - * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! - * - * @param x1 {number} The x-coordinate of the beginning of the arc - * @param y1 {number} The y-coordinate of the beginning of the arc - * @param x2 {number} The x-coordinate of the end of the arc - * @param y2 {number} The y-coordinate of the end of the arc - * @param radius {number} The radius of the arc - * @return {Graphics} - */ -Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points.push(x1, y1); - } - } - else - { - this.moveTo(x1, y1); - } - - var points = this.currentPath.shape.points, - fromX = points[points.length-2], - fromY = points[points.length-1], - a1 = fromY - y1, - b1 = fromX - x1, - a2 = y2 - y1, - b2 = x2 - x1, - mm = Math.abs(a1 * b2 - b1 * a2); - - if (mm < 1.0e-8 || radius === 0) - { - if (points[points.length-2] !== x1 || points[points.length-1] !== y1) - { - points.push(x1, y1); - } - } - else - { - var dd = a1 * a1 + b1 * b1, - cc = a2 * a2 + b2 * b2, - tt = a1 * a2 + b1 * b2, - k1 = radius * Math.sqrt(dd) / mm, - k2 = radius * Math.sqrt(cc) / mm, - j1 = k1 * tt / dd, - j2 = k2 * tt / cc, - cx = k1 * b2 + k2 * b1, - cy = k1 * a2 + k2 * a1, - px = b1 * (k2 + j1), - py = a1 * (k2 + j1), - qx = b2 * (k1 + j2), - qy = a2 * (k1 + j2), - startAngle = Math.atan2(py - cy, px - cx), - endAngle = Math.atan2(qy - cy, qx - cx); - - this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); - } - - this.dirty = true; - - return this; -}; - -/** - * The arc method creates an arc/curve (used to create circles, or parts of circles). - * - * @param cx {number} The x-coordinate of the center of the circle - * @param cy {number} The y-coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) - * @param endAngle {number} The ending angle, in radians - * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {Graphics} - */ -Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - var points; - - // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... - if (this.currentPath) - { - points = this.currentPath.shape.points; - - if (points.length === 0) - { - points.push(startX, startY); - } - else if (points[points.length-2] !== startX || points[points.length-1] !== startY) - { - points.push(startX, startY); - } - } - else - { - this.moveTo(startX, startY); - points = this.currentPath.shape.points; - } - - if (startAngle === endAngle) - { - return this; - } - - if (!anticlockwise && endAngle <= startAngle) - { - endAngle += Math.PI * 2; - } - else if (anticlockwise && startAngle <= endAngle) - { - startAngle += Math.PI * 2; - } - - var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); - var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; - - if (sweep === 0) - { - return this; - } - - var theta = sweep/(segs*2); - var theta2 = theta*2; - - var cTheta = Math.cos(theta); - var sTheta = Math.sin(theta); - - var segMinus = segs - 1; - - var remainder = ( segMinus % 1 ) / segMinus; - - for (var i = 0; i <= segMinus; ++i) - { - var real = i + remainder * i; - var angle = ((theta) + startAngle + (theta2 * real)); - - var c = Math.cos(angle); - var s = -Math.sin(angle); - - points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, - ( (cTheta * -s) + (sTheta * c) ) * radius + cy); - } - - this.dirty = true; - - return this; -}; - -/** - * Specifies a simple one-color fill that subsequent calls to other Graphics methods - * (such as lineTo() or drawCircle()) use when drawing. - * - * @param color {number} the color of the fill - * @param alpha {number} the alpha of the fill - * @return {Graphics} - */ -Graphics.prototype.beginFill = function (color, alpha) -{ - this.filling = true; - this.fillColor = color || 0; - this.fillAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length <= 2) - { - this.currentPath.fill = this.filling; - this.currentPath.fillColor = this.fillColor; - this.currentPath.fillAlpha = this.fillAlpha; - } - } - return this; -}; - -/** - * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. - * - * @return {Graphics} - */ -Graphics.prototype.endFill = function () -{ - this.filling = false; - this.fillColor = null; - this.fillAlpha = 1; - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @return {Graphics} - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {Graphics} - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {Graphics} - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {Array} The path data used to construct the polygon. - * @return {Graphics} - */ -Graphics.prototype.drawPolygon = function (path) -{ - if (!(path instanceof Array)) - { - path = Array.prototype.slice.call(arguments); - } - - this.drawShape(new math.Polygon(path)); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {Graphics} - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty = true; - this.clearDirty = true; - this.graphicsData = []; - - return this; -}; - -/** - * Useful function that returns a texture of the graphics object that can then be used to create sprites - * This can be quite useful if your geometry is complicated and needs to be reused multiple times. - * - * @param resolution {number} The resolution of the texture being generated - * @param scaleMode {number} Should be one of the scaleMode consts - * @return {Texture} a texture of the graphics object - */ -Graphics.prototype.generateTexture = function (resolution, scaleMode) -{ - resolution = resolution || 1; - - var bounds = this.getBounds(); - - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); - - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - canvasBuffer.context.scale(resolution, resolution); - - canvasBuffer.context.translate(-bounds.x,-bounds.y); - - CanvasGraphics.renderGraphics(this, canvasBuffer.context); - - return texture; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (this.isMask === true) - { - return; - } - - // this code may still be needed so leaving for now.. - // - /* - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture on the gpu too! - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.worldAlpha = this.worldAlpha; - - Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); - - return; - } - - */ - - if (this.glDirty) - { - this.dirty = true; - this.glDirty = false; - } - - renderer.setObjectRenderer(renderer.objectRenderers.graphics); - renderer.objectRenderers.graphics.render(this); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} - * @private - */ -Graphics.prototype.renderCanvas = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.alpha <= 0 || this.isMask === true) - { - return; - } - - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.alpha = this.alpha; - - Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); - - return; - } - else - { - var context = renderer.context; - var transform = this.worldTransform; - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - var resolution = renderer.resolution; - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } - } -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @return {Rectangle} the rectangular bounding area - */ -Graphics.prototype.getBounds = function (matrix) -{ - // return an empty object if the item is a mask! - if (this.isMask) - { - return math.Rectangle.EMPTY; - } - - if (this.dirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.dirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - return this._bounds; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; - - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; -}; - -/** - * Generates the cached sprite when the sprite has cacheAsBitmap = true - * - * @private - */ -Graphics.prototype._generateCachedSprite = function () -{ - var bounds = this.getLocalBounds(); - - if (!this._cachedSprite) - { - var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); - var texture = Texture.fromCanvas(canvasBuffer.canvas); - - this._cachedSprite = new Sprite(texture); - this._cachedSprite.buffer = canvasBuffer; - - this._cachedSprite.worldTransform = this.worldTransform; - } - else - { - this._cachedSprite.buffer.resize(bounds.width, bounds.height); - } - - // leverage the anchor to account for the offset of the element - this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); - this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); - - // this._cachedSprite.buffer.context.save(); - this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); - - // make sure we set the alpha of the graphics to 1 for the render.. - this.worldAlpha = 1; - - // now render the graphic.. - CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); - - this._cachedSprite.alpha = this.alpha; -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Graphics.prototype.updateCachedSpriteTexture = function () -{ - var cachedSprite = this._cachedSprite; - var texture = cachedSprite.texture; - var canvas = cachedSprite.buffer.canvas; - - texture.baseTexture.width = canvas.width; - texture.baseTexture.height = canvas.height; - texture.crop.width = texture.frame.width = canvas.width; - texture.crop.height = texture.frame.height = canvas.height; - - cachedSprite._width = canvas.width; - cachedSprite._height = canvas.height; - - // update the dirty base textures - texture.baseTexture.dirty(); -}; - -/** - * Destroys a previous cached sprite. - * - */ -Graphics.prototype.destroyCachedSprite = function () -{ - this._cachedSprite.texture.destroy(true); - - // let the gc collect the unused sprite - // TODO could be object pooled! - this._cachedSprite = null; -}; - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. - * @return {GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = this.filling; - this.currentPath = data; - } - - this.dirty = true; - - return data; -}; diff --git a/src/core/primitives/GraphicsData.js b/src/core/primitives/GraphicsData.js deleted file mode 100644 index 788f6f2..0000000 --- a/src/core/primitives/GraphicsData.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * A GraphicsData object. - * - * @class - * @namespace PIXI - */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - this.lineWidth = lineWidth; - this.lineColor = lineColor; - this.lineAlpha = lineAlpha; - this._lineTint = lineColor; - - this.fillColor = fillColor; - this.fillAlpha = fillAlpha; - this._fillTint = fillColor; - this.fill = fill; - - this.shape = shape; - this.type = shape.type; -} - -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {GraphicsData} - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js deleted file mode 100644 index 1a508f9..0000000 --- a/src/core/primitives/GraphicsRenderer.js +++ /dev/null @@ -1,874 +0,0 @@ -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; - -/** - * 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 ); - -// var matrix = graphics.worldTransform.clone(); -// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); -// matrix.append(graphics.worldTransform); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.maskManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.maskManager.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.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); - - 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/primitives/WebGLGraphicsData.js b/src/core/primitives/WebGLGraphicsData.js deleted file mode 100755 index 41be102..0000000 --- a/src/core/primitives/WebGLGraphicsData.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @class - * @private - */ -function WebGLGraphicsData(gl) { - this.gl = gl; - - //TODO does this need to be split before uploding?? - this.color = [0,0,0]; // color split! - this.points = []; - this.indices = []; - this.buffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - this.mode = 1; - this.alpha = 1; - this.dirty = true; -} - -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; -module.exports = WebGLGraphicsData; - -/** - * - */ -WebGLGraphicsData.prototype.reset = function () { - this.points = []; - this.indices = []; -}; - -/** - * - */ -WebGLGraphicsData.prototype.upload = function () { - var gl = this.gl; - -// this.lastIndex = graphics.graphicsData.length; - this.glPoints = new Float32Array(this.points); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); - - this.glIndicies = new Uint16Array(this.indices); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); - - this.dirty = false; -}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6c1fb7d..4e82baa 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -174,6 +174,9 @@ // time to create the render managers! each one focuses on managing a state in webGL + // initialize the context so it is ready for the managers. + this._initContext(); + /** * Deals with managing the shader programs and their attribs * @member {WebGLShaderManager} @@ -204,17 +207,11 @@ */ this.blendModeManager = new WebGLBlendModeManager(this); - - this.blendModes = null; this._boundUpdateTexture = this.updateTexture.bind(this); this._boundDestroyTexture = this.destroyTexture.bind(this); - - - // time init the context.. - this._initContext(); this.currentRenderTarget = this.renderTarget; @@ -228,27 +225,18 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - /** - * Manages the renderer of specific objects. - * - * @member {object} - */ - this.objectRenderers = {}; - this.currentRenderer = new ObjectRenderer(); - - // create an instance of each registered object renderer - for (var o in WebGLRenderer._objectRenderers) { - this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); - } - + this.initPlugins(); } // constructor WebGLRenderer.prototype.constructor = WebGLRenderer; module.exports = WebGLRenderer; +WebGLRenderer.glContextId = 0; + +utils.pluginTarget.mixin(WebGLRenderer); utils.eventTarget.mixin(WebGLRenderer.prototype); Object.defineProperties(WebGLRenderer.prototype, { @@ -297,11 +285,9 @@ gl.enable(gl.BLEND); this.renderTarget = new RenderTarget(this.gl, this.width, this.height, null, true); - this.emit('context', gl); - // now resize and we are good to go! this.resize(this.width, this.height); }; @@ -349,7 +335,7 @@ gl.clear(gl.COLOR_BUFFER_BIT); } - this.renderDisplayObject(object, this.renderTarget)//this.projection); + this.renderDisplayObject(object, this.renderTarget);//this.projection); }; /** @@ -536,12 +522,7 @@ this.maskManager.destroy(); this.filterManager.destroy(); - for (var o in this.objectRenderers) { - this.objectRenderers[o].destroy(); - this.objectRenderers[o] = null; - } - - this.objectRenderers = null; + this.destroyPlugins(); // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -611,11 +592,3 @@ this.blendModes[CONST.blendModes.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; } }; - -WebGLRenderer.glContextId = 0; -WebGLRenderer._objectRenderers = {}; - -WebGLRenderer.registerObjectRenderer = function (name, ctor) { - WebGLRenderer._objectRenderers[name] = ctor; -}; - diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js index 87c99f0..d6ed3c6 100644 --- a/src/core/renderers/webgl/managers/WebGLFilterManager.js +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -51,7 +51,7 @@ */ WebGLFilterManager.prototype.begin = function (buffer) { - this.defaultShader = this.renderer.shaderManager.defaultShader; + this.defaultShader = this.renderer.shaderManager.plugins.defaultShader; this.width = this.renderer.projection.x * 2; this.height = -this.renderer.projection.y * 2; @@ -385,7 +385,7 @@ if (!shader) { - shader = new Shader(gl); + shader = new Shader(this.renderer.shaderManager); shader.fragmentSrc = filter.fragmentSrc; shader.uniforms = filter.uniforms; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js index 6a1f782..d6a11c1 100644 --- a/src/core/renderers/webgl/managers/WebGLMaskManager.js +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -126,11 +126,11 @@ // bind the graphics object.. var projection = this.renderer.projection, offset = this.renderer.offset, - shader;// = this.renderer.shaderManager.primitiveShader; + shader;// = this.renderer.shaderManager.plugins.primitiveShader; if (webGLData.mode === 1) { - shader = this.renderer.shaderManager.complexPrimitiveShader; + shader = this.renderer.shaderManager.plugins.complexPrimitiveShader; this.renderer.shaderManager.setShader(shader); @@ -156,7 +156,7 @@ else { //this.renderer.shaderManager.activatePrimitiveShader(); - shader = this.renderer.shaderManager.primitiveShader; + shader = this.renderer.shaderManager.plugins.primitiveShader; this.renderer.shaderManager.setShader( shader ); gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); @@ -292,11 +292,11 @@ */ WebGLMaskManager.prototype.pushMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); if (maskData.dirty) { - this.renderer.objectRenderers.graphics.updateGraphics(maskData, this.renderer.gl); + this.renderer.plugins.graphics.updateGraphics(maskData, this.renderer.gl); } if (!maskData._webGL[this.renderer.gl.id].data.length) @@ -314,7 +314,7 @@ */ WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); this.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); }; diff --git a/src/core/renderers/webgl/managers/WebGLShaderManager.js b/src/core/renderers/webgl/managers/WebGLShaderManager.js index 90c6477..9ebb58e 100644 --- a/src/core/renderers/webgl/managers/WebGLShaderManager.js +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -1,9 +1,5 @@ var WebGLManager = require('./WebGLManager'), - PrimitiveShader = require('../shaders/PrimitiveShader'), - ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), - Shader = require('../shaders/Shader'), - FastShader = require('../shaders/FastShader'), - StripShader = require('../shaders/StripShader'); + utils = require('../../../utils'); /** * @class @@ -51,41 +47,16 @@ */ this.currentShader = null; - // this shader is used for rendering primitives - this.primitiveShader = null; - - // this shader is used for rendering triangle strips - this.complexPrimitiveShader = null; - - // this shader is used for the default sprite rendering - this.defaultShader = null; - - // this shader is used for the fast sprite rendering - this.fastShader = null; - - // the next one is used for rendering triangle strips - this.stripShader = null; + this.initPlugins(); // listen for context and update necessary shaders var self = this; - this.renderer.on('context', function (event) + this.renderer.on('context', function () { - var gl = event.data; - - // this shader is used for rendering primitives - self.primitiveShader = new PrimitiveShader(gl); - - // this shader is used for rendering triangle strips - self.complexPrimitiveShader = new ComplexPrimitiveShader(gl); - - // this shader is used for the default sprite rendering - self.defaultShader = new Shader(gl); - - // this shader is used for the fast sprite rendering - self.fastShader = new FastShader(gl); - - // the next one is used for rendering triangle strips - self.stripShader = new StripShader(gl); + for (var o in this.plugins) + { + this.plugins[o] = new (this.plugins[o].constructor)(self); + } self.setShader(self.defaultShader); }); @@ -95,6 +66,8 @@ WebGLShaderManager.prototype.constructor = WebGLShaderManager; module.exports = WebGLShaderManager; +utils.pluginTarget.mixin(WebGLShaderManager); + /** * Takes the attributes given in parameters. * @@ -164,24 +137,11 @@ */ WebGLShaderManager.prototype.destroy = function () { + this.destroyPlugins(); + this.attribState = null; this.tempAttribState = null; - this.primitiveShader.destroy(); - this.primitiveShader = null; - - this.complexPrimitiveShader.destroy(); - this.complexPrimitiveShader = null; - - this.defaultShader.destroy(); - this.defaultShader = null; - - this.fastShader.destroy(); - this.fastShader = null; - - this.stripShader.destroy(); - this.stripShader = null; - this.renderer = null; }; diff --git a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js b/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js deleted file mode 100644 index f8a07da..0000000 --- a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js +++ /dev/null @@ -1,53 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function ComplexPrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform vec3 tint;', - 'uniform float alpha;', - 'uniform vec3 color;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - color: { type: '3f', value: [0,0,0] }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); -ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; -module.exports = ComplexPrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/FastShader.js b/src/core/renderers/webgl/shaders/FastShader.js index d198f10..c80d040 100644 --- a/src/core/renderers/webgl/shaders/FastShader.js +++ b/src/core/renderers/webgl/shaders/FastShader.js @@ -1,15 +1,16 @@ -var Shader = require('./Shader'); +var Shader = require('./Shader'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @extends Shader * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. */ -function FastShader(gl) +function FastShader(shaderManager) { Shader.call(this, - gl, + shaderManager, // vertex shader [ 'attribute vec2 aVertexPosition;', @@ -70,3 +71,5 @@ FastShader.prototype = Object.create(Shader.prototype); FastShader.prototype.constructor = FastShader; module.exports = FastShader; + +WebGLShaderManager.registerPlugin('fastShader', FastShader); diff --git a/src/core/renderers/webgl/shaders/PrimitiveShader.js b/src/core/renderers/webgl/shaders/PrimitiveShader.js deleted file mode 100644 index 753c475..0000000 --- a/src/core/renderers/webgl/shaders/PrimitiveShader.js +++ /dev/null @@ -1,55 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform float alpha;', - 'uniform float flipY;', - 'uniform vec3 tint;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aColor:0 - } - ); -} - -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; -module.exports = PrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/Shader.js b/src/core/renderers/webgl/shaders/Shader.js index d065a76..c60c5f9 100644 --- a/src/core/renderers/webgl/shaders/Shader.js +++ b/src/core/renderers/webgl/shaders/Shader.js @@ -1,12 +1,16 @@ -var utils = require('../../../utils'); +var utils = require('../../../utils'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @namespace PIXI - * @param [fragmentSrc] {string} The source of the fragment shader. + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. * @param [vertexSrc] {string} The source of the vertex shader. + * @param [fragmentSrc] {string} The source of the fragment shader. + * @param customUniforms {object} Custom uniforms to use to augment the built-in ones. + * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Shader(gl, vertexSrc, fragmentSrc, customUniforms, customAttributes) +function Shader(shaderManager, vertexSrc, fragmentSrc, customUniforms, customAttributes) { /** * @member {number} @@ -18,7 +22,7 @@ * @member {WebGLContext} * @readonly */ - this.gl = gl; + this.gl = shaderManager.renderer.gl; /** * The WebGL program. @@ -31,8 +35,8 @@ uSampler: { type: 'sampler2D', value: 0 }, projectionVector: { type: '2f', value: { x: 0, y: 0 } }, offsetVector: { type: '2f', value: { x: 0, y: 0 } }, - projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, - 0, 1, 0, + projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, + 0, 1, 0, 0, 0, 1) }, dimensions: { type: '4f', value: new Float32Array(4) } }; @@ -103,6 +107,8 @@ Shader.prototype.constructor = Shader; module.exports = Shader; +WebGLShaderManager.registerPlugin('defaultShader', Shader); + Shader.prototype.init = function () { this.compile(); @@ -506,4 +512,4 @@ } return shader; -}; \ No newline at end of file +}; diff --git a/src/core/renderers/webgl/shaders/SpriteShader.js b/src/core/renderers/webgl/shaders/SpriteShader.js deleted file mode 100644 index 1805b0a..0000000 --- a/src/core/renderers/webgl/shaders/SpriteShader.js +++ /dev/null @@ -1,42 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function SpriteShader(gl) -{ - Shader.call(this, - gl, - null, - // fragment shader - [ - 'precision lowp float;', - - 'varying vec2 vTextureCoord;', - 'varying vec4 vColor;', - - 'uniform sampler2D uSampler;', - - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aTextureCoord: 0, - aColor: 0 - } - ); -} - -SpriteShader.prototype = Object.create(Shader.prototype); -SpriteShader.prototype.constructor = SpriteShader; -module.exports = SpriteShader; diff --git a/src/core/renderers/webgl/shaders/StripShader.js b/src/core/renderers/webgl/shaders/StripShader.js deleted file mode 100644 index f487381..0000000 --- a/src/core/renderers/webgl/shaders/StripShader.js +++ /dev/null @@ -1,54 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function StripShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - // 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform vec2 projectionVector;', - 'uniform vec2 offsetVector;', - - 'varying vec2 vTextureCoord;', - - 'void main(void){', - ' vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);', - ' v -= offsetVector.xyx;', - ' gl_Position = vec4( v.x / projectionVector.x -1.0, v.y / -projectionVector.y + 1.0 , 0.0, 1.0);', - ' vTextureCoord = aTextureCoord;', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'uniform float alpha;', - 'uniform sampler2D uSampler;', - - 'varying vec2 vTextureCoord;', - - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y)) * alpha;', - '}' - ].join('\n'), - // custom uniforms - { - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -StripShader.prototype = Object.create(Shader.prototype); -StripShader.prototype.constructor = StripShader; -module.exports = StripShader; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index 0a34e55..43e2d00 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -33,7 +33,7 @@ * @property texture * @type Any */ - this.texture = null + this.texture = null; this.width = 0; this.height = 0; @@ -56,11 +56,11 @@ this.frameBuffer = gl.createFramebuffer(); - /* + /* A frame buffer needs a target to render to.. create a texture and bind it attach it to the framebuffer.. */ - + this.texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, this.texture); @@ -70,13 +70,13 @@ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); 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); - + gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer ); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0); - - /* + + /* The stencil buffer is used for masking in pixi lets create one and then add attach it to the framebuffer.. */ @@ -86,7 +86,7 @@ } this.resize(width, height); - + }; RenderTarget.prototype.constructor = RenderTarget; @@ -94,21 +94,21 @@ /** * Clears the filter texture. -* +* * @method clear */ RenderTarget.prototype.clear = function() { var gl = this.gl; - + gl.clearColor(0,0,0, 0); gl.clear(gl.COLOR_BUFFER_BIT); }; RenderTarget.prototype.activate = function() { - this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer ); -} + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer); +}; /** * Resizes the texture to the specified width and height @@ -119,25 +119,25 @@ */ RenderTarget.prototype.resize = function(width, height) { - width = width | 0; - height = height | 0; - - if(this.width === width && this.height === height) return; + if (this.width === width && this.height === height) { + return; + } this.width = width; this.height = height; + this.projectionMatrix = new math.Matrix(); - - if(!this.root) + + if (!this.root) { var gl = this.gl; this.projectionMatrix.a = 1/width*2; - this.projectionMatrix.d = 1/height*2; + this.projectionMatrix.d = -1/height*2; this.projectionMatrix.tx = -1; - this.projectionMatrix.ty = -1; + this.projectionMatrix.ty = 1; gl.bindTexture(gl.TEXTURE_2D, this.texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width , height , 0, gl.RGBA, gl.UNSIGNED_BYTE, null); @@ -157,7 +157,7 @@ /** * Destroys the filter texture. -* +* * @method destroy */ RenderTarget.prototype.destroy = function() diff --git a/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js b/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js index a63f4d7..3f37b7d 100644 --- a/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js +++ b/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js @@ -183,7 +183,7 @@ */ WebGLFastSpriteBatch.prototype.begin = function (spriteBatch) { - this.shader = this.renderer.shaderManager.fastShader; + this.shader = this.renderer.shaderManager.plugins.fastShader; this.matrix = spriteBatch.worldTransform.toArray(true); diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index bf8db28..2dc47ff 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -201,8 +201,8 @@ Sprite.prototype._renderWebGL = function (renderer) { - renderer.setObjectRenderer(renderer.objectRenderers.sprite); - renderer.objectRenderers.sprite.render(this); + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); }; /** diff --git a/src/core/sprites/SpriteBatch.js b/src/core/sprites/SpriteBatch.js index 63cebc0..4fe33e4 100644 --- a/src/core/sprites/SpriteBatch.js +++ b/src/core/sprites/SpriteBatch.js @@ -61,7 +61,7 @@ renderer.spriteBatch.stop(); - renderer.shaderManager.setShader(renderer.shaderManager.fastShader); + renderer.shaderManager.setShader(renderer.shaderManager.plugins.fastShader); renderer.fastSpriteBatch.begin(this); renderer.fastSpriteBatch.render(this); diff --git a/src/core/sprites/SpriteRenderer.js b/src/core/sprites/SpriteRenderer.js deleted file mode 100644 index fd9a802..0000000 --- a/src/core/sprites/SpriteRenderer.js +++ /dev/null @@ -1,537 +0,0 @@ -var ObjectRenderer = require('../renderers/webgl/utils/ObjectRenderer'), - Shader = require('../renderers/webgl/shaders/Shader'), - SpriteShader = require('../renderers/webgl/shaders/SpriteShader'), - WebGLRenderer = require('../renderers/webgl/WebGLRenderer'); - math = require('../math'); - -/** - * @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.setupContext(); -} - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; -module.exports = 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 = sprite; - 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 - gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, this.renderer.currentRenderTarget.projectionMatrix.toArray(true)); - } - } - - 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; -}; diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js new file mode 100644 index 0000000..41be102 --- /dev/null +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -0,0 +1,48 @@ +/** + * @class + * @private + */ +function WebGLGraphicsData(gl) { + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +} + +WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; +module.exports = WebGLGraphicsData; + +/** + * + */ +WebGLGraphicsData.prototype.reset = function () { + this.points = []; + this.indices = []; +}; + +/** + * + */ +WebGLGraphicsData.prototype.upload = function () { + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; diff --git a/src/core/index.js b/src/core/index.js index 6f744e9..1d0a398 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -23,12 +23,12 @@ Sprite: require('./sprites/Sprite'), SpriteBatch: require('./sprites/SpriteBatch'), - SpriteRenderer: require('./sprites/SpriteRenderer'), + SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), // primitives - Graphics: require('./primitives/Graphics'), - GraphicsData: require('./primitives/GraphicsData'), - GraphicsRenderer: require('./primitives/GraphicsRenderer'), + Graphics: require('./graphics/Graphics'), + GraphicsData: require('./graphics/GraphicsData'), + GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,10 +42,8 @@ CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), // renderers - webgl - WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), - // WebGLGraphics: require('./renderers/webgl/utils/WebGLGraphics'), - - + WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), + Shader: require('./renderers/webgl/shaders/Shader'), /** * This helper function will automatically detect which renderer you should be using. @@ -103,7 +101,3 @@ return core.autoDetectRenderer(width, height, options, isAndroid); } }; - -// Adding here for now.. sure there is a better place! -core.WebGLRenderer.registerObjectRenderer('sprite', core.SpriteRenderer); -core.WebGLRenderer.registerObjectRenderer('graphics', core.GraphicsRenderer); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index a086950..81de05c 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -268,6 +268,6 @@ matrix.ty = this.ty; return matrix; -} +}; Matrix.IDENTITY = new Matrix(); diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js deleted file mode 100644 index 8db9b45..0000000 --- a/src/core/primitives/Graphics.js +++ /dev/null @@ -1,1116 +0,0 @@ -var DisplayObjectContainer = require('../display/DisplayObjectContainer'), - //Sprite = require('../display/Sprite'), - Texture = require('../textures/Texture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), - // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), - GraphicsData = require('./GraphicsData'), - math = require('../math'), - CONST = require('../const'); - -/** - * The Graphics class contains methods used to draw primitive shapes such as lines, circles and - * rectangles to the display, and color and fill them. - * - * @class - * @extends DisplayObjectContainer - * @namespace PIXI - */ -function Graphics() -{ - DisplayObjectContainer.call(this); - - this.renderable = true; - - /** - * The alpha value used when filling the Graphics object. - * - * @member {number} - * @default 1 - */ - this.fillAlpha = 1; - - /** - * The width (thickness) of any lines drawn. - * - * @member {number} - * @default 0 - */ - this.lineWidth = 0; - - /** - * The color of any lines drawn. - * - * @member {string} - * @default 0 - */ - this.lineColor = 0; - - /** - * Graphics data - * - * @member {GraphicsData[]} - * @private - */ - this.graphicsData = []; - - /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * Current path - * - * @member {GraphicsData} - * @private - */ - this.currentPath = null; - - /** - * Array containing some WebGL-related properties used by the WebGL renderer. - * - * @member {object} - * @private - */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; - - /** - * Whether this shape is being used as a mask. - * - * @member {boolean} - */ - this.isMask = false; - - /** - * The bounds' padding used for bounds calculation. - * - * @member {number} - */ - this.boundsPadding = 0; - - /** - * A cache of the local bounds to prevent recalculation. - * - * @member {Rectangle} - * @private - */ - this._localBounds = new math.Rectangle(0,0,1,1); - - /** - * Used to detect if the graphics object has changed. If this is set to true then the graphics - * object will be recalculated. - * - * @member {boolean} - * @private - */ - this.dirty = true; - - /** - * Used to detect if the WebGL graphics object has changed. If this is set to true then the - * graphics object will be recalculated. - * - * @member {boolean} - * @private - */ - this.glDirty = false; - - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; -} - -// constructor -Graphics.prototype = Object.create(DisplayObjectContainer.prototype); -Graphics.prototype.constructor = Graphics; -module.exports = Graphics; - -Object.defineProperties(Graphics.prototype, { - /** - * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. - * This is useful if your graphics element does not change often, as it will speed up the rendering - * of the object in exchange for taking up texture memory. It is also useful if you need the graphics - * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if - * you are constantly redrawing the graphics element. - * - * @member {boolean} - * @memberof Graphics# - * @default false - * @private - */ - cacheAsBitmap: { - get: function () - { - return this._cacheAsBitmap; - }, - set: function (value) - { - this._cacheAsBitmap = value; - - if (this._cacheAsBitmap) - { - this._generateCachedSprite(); - } - else - { - this.destroyCachedSprite(); - this.dirty = true; - } - } - } -}); - -/** - * Creates a new Graphics object with the same values as this one. - * - * @return {Graphics} - */ -GraphicsData.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = this.dirty; - clone.glDirty = this.glDirty; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData.clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {Graphics} - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (arguments.length < 3) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); - } - else - { - // otherwise its empty so lets just set the line properties - this.currentPath.lineWidth = this.lineWidth; - this.currentPath.lineColor = this.lineColor; - this.currentPath.lineAlpha = this.lineAlpha; - } - } - - return this; -}; - -/** - * Moves the current drawing position to x, y. - * - * @param x {number} the X coordinate to move to - * @param y {number} the Y coordinate to move to - * @return {Graphics} - */ -Graphics.prototype.moveTo = function (x, y) -{ - this.drawShape(new math.Polygon([x,y])); - - return this; -}; - -/** - * Draws a line using the current line style from the current drawing position to (x, y); - * The current drawing position is then set to (x, y). - * - * @param x {number} the X coordinate to draw to - * @param y {number} the Y coordinate to draw to - * @return {Graphics} - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a quadratic bezier curve and then draws it. - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @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 {Graphics} - */ -Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var xa, - ya, - n = 20, - points = this.currentPath.shape.points; - - if (points.length === 0) - { - this.moveTo(0, 0); - } - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - for (var i = 1; i <= n; ++i) - { - j = i / n; - - xa = fromX + ( (cpX - fromX) * j ); - ya = fromY + ( (cpY - fromY) * j ); - - points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), - ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); - } - - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a bezier curve and then draws it. - * - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param cpX2 {number} Second Control point x - * @param cpY2 {number} Second Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {Graphics} - */ -Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var n = 20, - dt, - dt2, - dt3, - t2, - t3, - points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - - for (var i = 1; i <= n; ++i) - { - j = i / n; - - dt = (1 - j); - dt2 = dt * dt; - dt3 = dt2 * dt; - - t2 = j * j; - t3 = t2 * j; - - points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, - dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); - } - - this.dirty = true; - - return this; -}; - -/** - * The arcTo() method creates an arc/curve between two tangents on the canvas. - * - * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! - * - * @param x1 {number} The x-coordinate of the beginning of the arc - * @param y1 {number} The y-coordinate of the beginning of the arc - * @param x2 {number} The x-coordinate of the end of the arc - * @param y2 {number} The y-coordinate of the end of the arc - * @param radius {number} The radius of the arc - * @return {Graphics} - */ -Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points.push(x1, y1); - } - } - else - { - this.moveTo(x1, y1); - } - - var points = this.currentPath.shape.points, - fromX = points[points.length-2], - fromY = points[points.length-1], - a1 = fromY - y1, - b1 = fromX - x1, - a2 = y2 - y1, - b2 = x2 - x1, - mm = Math.abs(a1 * b2 - b1 * a2); - - if (mm < 1.0e-8 || radius === 0) - { - if (points[points.length-2] !== x1 || points[points.length-1] !== y1) - { - points.push(x1, y1); - } - } - else - { - var dd = a1 * a1 + b1 * b1, - cc = a2 * a2 + b2 * b2, - tt = a1 * a2 + b1 * b2, - k1 = radius * Math.sqrt(dd) / mm, - k2 = radius * Math.sqrt(cc) / mm, - j1 = k1 * tt / dd, - j2 = k2 * tt / cc, - cx = k1 * b2 + k2 * b1, - cy = k1 * a2 + k2 * a1, - px = b1 * (k2 + j1), - py = a1 * (k2 + j1), - qx = b2 * (k1 + j2), - qy = a2 * (k1 + j2), - startAngle = Math.atan2(py - cy, px - cx), - endAngle = Math.atan2(qy - cy, qx - cx); - - this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); - } - - this.dirty = true; - - return this; -}; - -/** - * The arc method creates an arc/curve (used to create circles, or parts of circles). - * - * @param cx {number} The x-coordinate of the center of the circle - * @param cy {number} The y-coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) - * @param endAngle {number} The ending angle, in radians - * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {Graphics} - */ -Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - var points; - - // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... - if (this.currentPath) - { - points = this.currentPath.shape.points; - - if (points.length === 0) - { - points.push(startX, startY); - } - else if (points[points.length-2] !== startX || points[points.length-1] !== startY) - { - points.push(startX, startY); - } - } - else - { - this.moveTo(startX, startY); - points = this.currentPath.shape.points; - } - - if (startAngle === endAngle) - { - return this; - } - - if (!anticlockwise && endAngle <= startAngle) - { - endAngle += Math.PI * 2; - } - else if (anticlockwise && startAngle <= endAngle) - { - startAngle += Math.PI * 2; - } - - var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); - var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; - - if (sweep === 0) - { - return this; - } - - var theta = sweep/(segs*2); - var theta2 = theta*2; - - var cTheta = Math.cos(theta); - var sTheta = Math.sin(theta); - - var segMinus = segs - 1; - - var remainder = ( segMinus % 1 ) / segMinus; - - for (var i = 0; i <= segMinus; ++i) - { - var real = i + remainder * i; - var angle = ((theta) + startAngle + (theta2 * real)); - - var c = Math.cos(angle); - var s = -Math.sin(angle); - - points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, - ( (cTheta * -s) + (sTheta * c) ) * radius + cy); - } - - this.dirty = true; - - return this; -}; - -/** - * Specifies a simple one-color fill that subsequent calls to other Graphics methods - * (such as lineTo() or drawCircle()) use when drawing. - * - * @param color {number} the color of the fill - * @param alpha {number} the alpha of the fill - * @return {Graphics} - */ -Graphics.prototype.beginFill = function (color, alpha) -{ - this.filling = true; - this.fillColor = color || 0; - this.fillAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length <= 2) - { - this.currentPath.fill = this.filling; - this.currentPath.fillColor = this.fillColor; - this.currentPath.fillAlpha = this.fillAlpha; - } - } - return this; -}; - -/** - * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. - * - * @return {Graphics} - */ -Graphics.prototype.endFill = function () -{ - this.filling = false; - this.fillColor = null; - this.fillAlpha = 1; - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @return {Graphics} - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {Graphics} - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {Graphics} - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {Array} The path data used to construct the polygon. - * @return {Graphics} - */ -Graphics.prototype.drawPolygon = function (path) -{ - if (!(path instanceof Array)) - { - path = Array.prototype.slice.call(arguments); - } - - this.drawShape(new math.Polygon(path)); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {Graphics} - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty = true; - this.clearDirty = true; - this.graphicsData = []; - - return this; -}; - -/** - * Useful function that returns a texture of the graphics object that can then be used to create sprites - * This can be quite useful if your geometry is complicated and needs to be reused multiple times. - * - * @param resolution {number} The resolution of the texture being generated - * @param scaleMode {number} Should be one of the scaleMode consts - * @return {Texture} a texture of the graphics object - */ -Graphics.prototype.generateTexture = function (resolution, scaleMode) -{ - resolution = resolution || 1; - - var bounds = this.getBounds(); - - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); - - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - canvasBuffer.context.scale(resolution, resolution); - - canvasBuffer.context.translate(-bounds.x,-bounds.y); - - CanvasGraphics.renderGraphics(this, canvasBuffer.context); - - return texture; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (this.isMask === true) - { - return; - } - - // this code may still be needed so leaving for now.. - // - /* - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture on the gpu too! - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.worldAlpha = this.worldAlpha; - - Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); - - return; - } - - */ - - if (this.glDirty) - { - this.dirty = true; - this.glDirty = false; - } - - renderer.setObjectRenderer(renderer.objectRenderers.graphics); - renderer.objectRenderers.graphics.render(this); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} - * @private - */ -Graphics.prototype.renderCanvas = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.alpha <= 0 || this.isMask === true) - { - return; - } - - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.alpha = this.alpha; - - Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); - - return; - } - else - { - var context = renderer.context; - var transform = this.worldTransform; - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - var resolution = renderer.resolution; - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } - } -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @return {Rectangle} the rectangular bounding area - */ -Graphics.prototype.getBounds = function (matrix) -{ - // return an empty object if the item is a mask! - if (this.isMask) - { - return math.Rectangle.EMPTY; - } - - if (this.dirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.dirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - return this._bounds; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; - - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; -}; - -/** - * Generates the cached sprite when the sprite has cacheAsBitmap = true - * - * @private - */ -Graphics.prototype._generateCachedSprite = function () -{ - var bounds = this.getLocalBounds(); - - if (!this._cachedSprite) - { - var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); - var texture = Texture.fromCanvas(canvasBuffer.canvas); - - this._cachedSprite = new Sprite(texture); - this._cachedSprite.buffer = canvasBuffer; - - this._cachedSprite.worldTransform = this.worldTransform; - } - else - { - this._cachedSprite.buffer.resize(bounds.width, bounds.height); - } - - // leverage the anchor to account for the offset of the element - this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); - this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); - - // this._cachedSprite.buffer.context.save(); - this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); - - // make sure we set the alpha of the graphics to 1 for the render.. - this.worldAlpha = 1; - - // now render the graphic.. - CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); - - this._cachedSprite.alpha = this.alpha; -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Graphics.prototype.updateCachedSpriteTexture = function () -{ - var cachedSprite = this._cachedSprite; - var texture = cachedSprite.texture; - var canvas = cachedSprite.buffer.canvas; - - texture.baseTexture.width = canvas.width; - texture.baseTexture.height = canvas.height; - texture.crop.width = texture.frame.width = canvas.width; - texture.crop.height = texture.frame.height = canvas.height; - - cachedSprite._width = canvas.width; - cachedSprite._height = canvas.height; - - // update the dirty base textures - texture.baseTexture.dirty(); -}; - -/** - * Destroys a previous cached sprite. - * - */ -Graphics.prototype.destroyCachedSprite = function () -{ - this._cachedSprite.texture.destroy(true); - - // let the gc collect the unused sprite - // TODO could be object pooled! - this._cachedSprite = null; -}; - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. - * @return {GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = this.filling; - this.currentPath = data; - } - - this.dirty = true; - - return data; -}; diff --git a/src/core/primitives/GraphicsData.js b/src/core/primitives/GraphicsData.js deleted file mode 100644 index 788f6f2..0000000 --- a/src/core/primitives/GraphicsData.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * A GraphicsData object. - * - * @class - * @namespace PIXI - */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - this.lineWidth = lineWidth; - this.lineColor = lineColor; - this.lineAlpha = lineAlpha; - this._lineTint = lineColor; - - this.fillColor = fillColor; - this.fillAlpha = fillAlpha; - this._fillTint = fillColor; - this.fill = fill; - - this.shape = shape; - this.type = shape.type; -} - -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {GraphicsData} - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js deleted file mode 100644 index 1a508f9..0000000 --- a/src/core/primitives/GraphicsRenderer.js +++ /dev/null @@ -1,874 +0,0 @@ -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; - -/** - * 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 ); - -// var matrix = graphics.worldTransform.clone(); -// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); -// matrix.append(graphics.worldTransform); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.maskManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.maskManager.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.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); - - 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/primitives/WebGLGraphicsData.js b/src/core/primitives/WebGLGraphicsData.js deleted file mode 100755 index 41be102..0000000 --- a/src/core/primitives/WebGLGraphicsData.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @class - * @private - */ -function WebGLGraphicsData(gl) { - this.gl = gl; - - //TODO does this need to be split before uploding?? - this.color = [0,0,0]; // color split! - this.points = []; - this.indices = []; - this.buffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - this.mode = 1; - this.alpha = 1; - this.dirty = true; -} - -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; -module.exports = WebGLGraphicsData; - -/** - * - */ -WebGLGraphicsData.prototype.reset = function () { - this.points = []; - this.indices = []; -}; - -/** - * - */ -WebGLGraphicsData.prototype.upload = function () { - var gl = this.gl; - -// this.lastIndex = graphics.graphicsData.length; - this.glPoints = new Float32Array(this.points); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); - - this.glIndicies = new Uint16Array(this.indices); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); - - this.dirty = false; -}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6c1fb7d..4e82baa 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -174,6 +174,9 @@ // time to create the render managers! each one focuses on managing a state in webGL + // initialize the context so it is ready for the managers. + this._initContext(); + /** * Deals with managing the shader programs and their attribs * @member {WebGLShaderManager} @@ -204,17 +207,11 @@ */ this.blendModeManager = new WebGLBlendModeManager(this); - - this.blendModes = null; this._boundUpdateTexture = this.updateTexture.bind(this); this._boundDestroyTexture = this.destroyTexture.bind(this); - - - // time init the context.. - this._initContext(); this.currentRenderTarget = this.renderTarget; @@ -228,27 +225,18 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - /** - * Manages the renderer of specific objects. - * - * @member {object} - */ - this.objectRenderers = {}; - this.currentRenderer = new ObjectRenderer(); - - // create an instance of each registered object renderer - for (var o in WebGLRenderer._objectRenderers) { - this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); - } - + this.initPlugins(); } // constructor WebGLRenderer.prototype.constructor = WebGLRenderer; module.exports = WebGLRenderer; +WebGLRenderer.glContextId = 0; + +utils.pluginTarget.mixin(WebGLRenderer); utils.eventTarget.mixin(WebGLRenderer.prototype); Object.defineProperties(WebGLRenderer.prototype, { @@ -297,11 +285,9 @@ gl.enable(gl.BLEND); this.renderTarget = new RenderTarget(this.gl, this.width, this.height, null, true); - this.emit('context', gl); - // now resize and we are good to go! this.resize(this.width, this.height); }; @@ -349,7 +335,7 @@ gl.clear(gl.COLOR_BUFFER_BIT); } - this.renderDisplayObject(object, this.renderTarget)//this.projection); + this.renderDisplayObject(object, this.renderTarget);//this.projection); }; /** @@ -536,12 +522,7 @@ this.maskManager.destroy(); this.filterManager.destroy(); - for (var o in this.objectRenderers) { - this.objectRenderers[o].destroy(); - this.objectRenderers[o] = null; - } - - this.objectRenderers = null; + this.destroyPlugins(); // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -611,11 +592,3 @@ this.blendModes[CONST.blendModes.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; } }; - -WebGLRenderer.glContextId = 0; -WebGLRenderer._objectRenderers = {}; - -WebGLRenderer.registerObjectRenderer = function (name, ctor) { - WebGLRenderer._objectRenderers[name] = ctor; -}; - diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js index 87c99f0..d6ed3c6 100644 --- a/src/core/renderers/webgl/managers/WebGLFilterManager.js +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -51,7 +51,7 @@ */ WebGLFilterManager.prototype.begin = function (buffer) { - this.defaultShader = this.renderer.shaderManager.defaultShader; + this.defaultShader = this.renderer.shaderManager.plugins.defaultShader; this.width = this.renderer.projection.x * 2; this.height = -this.renderer.projection.y * 2; @@ -385,7 +385,7 @@ if (!shader) { - shader = new Shader(gl); + shader = new Shader(this.renderer.shaderManager); shader.fragmentSrc = filter.fragmentSrc; shader.uniforms = filter.uniforms; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js index 6a1f782..d6a11c1 100644 --- a/src/core/renderers/webgl/managers/WebGLMaskManager.js +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -126,11 +126,11 @@ // bind the graphics object.. var projection = this.renderer.projection, offset = this.renderer.offset, - shader;// = this.renderer.shaderManager.primitiveShader; + shader;// = this.renderer.shaderManager.plugins.primitiveShader; if (webGLData.mode === 1) { - shader = this.renderer.shaderManager.complexPrimitiveShader; + shader = this.renderer.shaderManager.plugins.complexPrimitiveShader; this.renderer.shaderManager.setShader(shader); @@ -156,7 +156,7 @@ else { //this.renderer.shaderManager.activatePrimitiveShader(); - shader = this.renderer.shaderManager.primitiveShader; + shader = this.renderer.shaderManager.plugins.primitiveShader; this.renderer.shaderManager.setShader( shader ); gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); @@ -292,11 +292,11 @@ */ WebGLMaskManager.prototype.pushMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); if (maskData.dirty) { - this.renderer.objectRenderers.graphics.updateGraphics(maskData, this.renderer.gl); + this.renderer.plugins.graphics.updateGraphics(maskData, this.renderer.gl); } if (!maskData._webGL[this.renderer.gl.id].data.length) @@ -314,7 +314,7 @@ */ WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); this.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); }; diff --git a/src/core/renderers/webgl/managers/WebGLShaderManager.js b/src/core/renderers/webgl/managers/WebGLShaderManager.js index 90c6477..9ebb58e 100644 --- a/src/core/renderers/webgl/managers/WebGLShaderManager.js +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -1,9 +1,5 @@ var WebGLManager = require('./WebGLManager'), - PrimitiveShader = require('../shaders/PrimitiveShader'), - ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), - Shader = require('../shaders/Shader'), - FastShader = require('../shaders/FastShader'), - StripShader = require('../shaders/StripShader'); + utils = require('../../../utils'); /** * @class @@ -51,41 +47,16 @@ */ this.currentShader = null; - // this shader is used for rendering primitives - this.primitiveShader = null; - - // this shader is used for rendering triangle strips - this.complexPrimitiveShader = null; - - // this shader is used for the default sprite rendering - this.defaultShader = null; - - // this shader is used for the fast sprite rendering - this.fastShader = null; - - // the next one is used for rendering triangle strips - this.stripShader = null; + this.initPlugins(); // listen for context and update necessary shaders var self = this; - this.renderer.on('context', function (event) + this.renderer.on('context', function () { - var gl = event.data; - - // this shader is used for rendering primitives - self.primitiveShader = new PrimitiveShader(gl); - - // this shader is used for rendering triangle strips - self.complexPrimitiveShader = new ComplexPrimitiveShader(gl); - - // this shader is used for the default sprite rendering - self.defaultShader = new Shader(gl); - - // this shader is used for the fast sprite rendering - self.fastShader = new FastShader(gl); - - // the next one is used for rendering triangle strips - self.stripShader = new StripShader(gl); + for (var o in this.plugins) + { + this.plugins[o] = new (this.plugins[o].constructor)(self); + } self.setShader(self.defaultShader); }); @@ -95,6 +66,8 @@ WebGLShaderManager.prototype.constructor = WebGLShaderManager; module.exports = WebGLShaderManager; +utils.pluginTarget.mixin(WebGLShaderManager); + /** * Takes the attributes given in parameters. * @@ -164,24 +137,11 @@ */ WebGLShaderManager.prototype.destroy = function () { + this.destroyPlugins(); + this.attribState = null; this.tempAttribState = null; - this.primitiveShader.destroy(); - this.primitiveShader = null; - - this.complexPrimitiveShader.destroy(); - this.complexPrimitiveShader = null; - - this.defaultShader.destroy(); - this.defaultShader = null; - - this.fastShader.destroy(); - this.fastShader = null; - - this.stripShader.destroy(); - this.stripShader = null; - this.renderer = null; }; diff --git a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js b/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js deleted file mode 100644 index f8a07da..0000000 --- a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js +++ /dev/null @@ -1,53 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function ComplexPrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform vec3 tint;', - 'uniform float alpha;', - 'uniform vec3 color;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - color: { type: '3f', value: [0,0,0] }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); -ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; -module.exports = ComplexPrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/FastShader.js b/src/core/renderers/webgl/shaders/FastShader.js index d198f10..c80d040 100644 --- a/src/core/renderers/webgl/shaders/FastShader.js +++ b/src/core/renderers/webgl/shaders/FastShader.js @@ -1,15 +1,16 @@ -var Shader = require('./Shader'); +var Shader = require('./Shader'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @extends Shader * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. */ -function FastShader(gl) +function FastShader(shaderManager) { Shader.call(this, - gl, + shaderManager, // vertex shader [ 'attribute vec2 aVertexPosition;', @@ -70,3 +71,5 @@ FastShader.prototype = Object.create(Shader.prototype); FastShader.prototype.constructor = FastShader; module.exports = FastShader; + +WebGLShaderManager.registerPlugin('fastShader', FastShader); diff --git a/src/core/renderers/webgl/shaders/PrimitiveShader.js b/src/core/renderers/webgl/shaders/PrimitiveShader.js deleted file mode 100644 index 753c475..0000000 --- a/src/core/renderers/webgl/shaders/PrimitiveShader.js +++ /dev/null @@ -1,55 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform float alpha;', - 'uniform float flipY;', - 'uniform vec3 tint;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aColor:0 - } - ); -} - -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; -module.exports = PrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/Shader.js b/src/core/renderers/webgl/shaders/Shader.js index d065a76..c60c5f9 100644 --- a/src/core/renderers/webgl/shaders/Shader.js +++ b/src/core/renderers/webgl/shaders/Shader.js @@ -1,12 +1,16 @@ -var utils = require('../../../utils'); +var utils = require('../../../utils'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @namespace PIXI - * @param [fragmentSrc] {string} The source of the fragment shader. + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. * @param [vertexSrc] {string} The source of the vertex shader. + * @param [fragmentSrc] {string} The source of the fragment shader. + * @param customUniforms {object} Custom uniforms to use to augment the built-in ones. + * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Shader(gl, vertexSrc, fragmentSrc, customUniforms, customAttributes) +function Shader(shaderManager, vertexSrc, fragmentSrc, customUniforms, customAttributes) { /** * @member {number} @@ -18,7 +22,7 @@ * @member {WebGLContext} * @readonly */ - this.gl = gl; + this.gl = shaderManager.renderer.gl; /** * The WebGL program. @@ -31,8 +35,8 @@ uSampler: { type: 'sampler2D', value: 0 }, projectionVector: { type: '2f', value: { x: 0, y: 0 } }, offsetVector: { type: '2f', value: { x: 0, y: 0 } }, - projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, - 0, 1, 0, + projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, + 0, 1, 0, 0, 0, 1) }, dimensions: { type: '4f', value: new Float32Array(4) } }; @@ -103,6 +107,8 @@ Shader.prototype.constructor = Shader; module.exports = Shader; +WebGLShaderManager.registerPlugin('defaultShader', Shader); + Shader.prototype.init = function () { this.compile(); @@ -506,4 +512,4 @@ } return shader; -}; \ No newline at end of file +}; diff --git a/src/core/renderers/webgl/shaders/SpriteShader.js b/src/core/renderers/webgl/shaders/SpriteShader.js deleted file mode 100644 index 1805b0a..0000000 --- a/src/core/renderers/webgl/shaders/SpriteShader.js +++ /dev/null @@ -1,42 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function SpriteShader(gl) -{ - Shader.call(this, - gl, - null, - // fragment shader - [ - 'precision lowp float;', - - 'varying vec2 vTextureCoord;', - 'varying vec4 vColor;', - - 'uniform sampler2D uSampler;', - - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aTextureCoord: 0, - aColor: 0 - } - ); -} - -SpriteShader.prototype = Object.create(Shader.prototype); -SpriteShader.prototype.constructor = SpriteShader; -module.exports = SpriteShader; diff --git a/src/core/renderers/webgl/shaders/StripShader.js b/src/core/renderers/webgl/shaders/StripShader.js deleted file mode 100644 index f487381..0000000 --- a/src/core/renderers/webgl/shaders/StripShader.js +++ /dev/null @@ -1,54 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function StripShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - // 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform vec2 projectionVector;', - 'uniform vec2 offsetVector;', - - 'varying vec2 vTextureCoord;', - - 'void main(void){', - ' vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);', - ' v -= offsetVector.xyx;', - ' gl_Position = vec4( v.x / projectionVector.x -1.0, v.y / -projectionVector.y + 1.0 , 0.0, 1.0);', - ' vTextureCoord = aTextureCoord;', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'uniform float alpha;', - 'uniform sampler2D uSampler;', - - 'varying vec2 vTextureCoord;', - - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y)) * alpha;', - '}' - ].join('\n'), - // custom uniforms - { - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -StripShader.prototype = Object.create(Shader.prototype); -StripShader.prototype.constructor = StripShader; -module.exports = StripShader; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index 0a34e55..43e2d00 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -33,7 +33,7 @@ * @property texture * @type Any */ - this.texture = null + this.texture = null; this.width = 0; this.height = 0; @@ -56,11 +56,11 @@ this.frameBuffer = gl.createFramebuffer(); - /* + /* A frame buffer needs a target to render to.. create a texture and bind it attach it to the framebuffer.. */ - + this.texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, this.texture); @@ -70,13 +70,13 @@ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); 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); - + gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer ); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0); - - /* + + /* The stencil buffer is used for masking in pixi lets create one and then add attach it to the framebuffer.. */ @@ -86,7 +86,7 @@ } this.resize(width, height); - + }; RenderTarget.prototype.constructor = RenderTarget; @@ -94,21 +94,21 @@ /** * Clears the filter texture. -* +* * @method clear */ RenderTarget.prototype.clear = function() { var gl = this.gl; - + gl.clearColor(0,0,0, 0); gl.clear(gl.COLOR_BUFFER_BIT); }; RenderTarget.prototype.activate = function() { - this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer ); -} + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer); +}; /** * Resizes the texture to the specified width and height @@ -119,25 +119,25 @@ */ RenderTarget.prototype.resize = function(width, height) { - width = width | 0; - height = height | 0; - - if(this.width === width && this.height === height) return; + if (this.width === width && this.height === height) { + return; + } this.width = width; this.height = height; + this.projectionMatrix = new math.Matrix(); - - if(!this.root) + + if (!this.root) { var gl = this.gl; this.projectionMatrix.a = 1/width*2; - this.projectionMatrix.d = 1/height*2; + this.projectionMatrix.d = -1/height*2; this.projectionMatrix.tx = -1; - this.projectionMatrix.ty = -1; + this.projectionMatrix.ty = 1; gl.bindTexture(gl.TEXTURE_2D, this.texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width , height , 0, gl.RGBA, gl.UNSIGNED_BYTE, null); @@ -157,7 +157,7 @@ /** * Destroys the filter texture. -* +* * @method destroy */ RenderTarget.prototype.destroy = function() diff --git a/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js b/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js index a63f4d7..3f37b7d 100644 --- a/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js +++ b/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js @@ -183,7 +183,7 @@ */ WebGLFastSpriteBatch.prototype.begin = function (spriteBatch) { - this.shader = this.renderer.shaderManager.fastShader; + this.shader = this.renderer.shaderManager.plugins.fastShader; this.matrix = spriteBatch.worldTransform.toArray(true); diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index bf8db28..2dc47ff 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -201,8 +201,8 @@ Sprite.prototype._renderWebGL = function (renderer) { - renderer.setObjectRenderer(renderer.objectRenderers.sprite); - renderer.objectRenderers.sprite.render(this); + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); }; /** diff --git a/src/core/sprites/SpriteBatch.js b/src/core/sprites/SpriteBatch.js index 63cebc0..4fe33e4 100644 --- a/src/core/sprites/SpriteBatch.js +++ b/src/core/sprites/SpriteBatch.js @@ -61,7 +61,7 @@ renderer.spriteBatch.stop(); - renderer.shaderManager.setShader(renderer.shaderManager.fastShader); + renderer.shaderManager.setShader(renderer.shaderManager.plugins.fastShader); renderer.fastSpriteBatch.begin(this); renderer.fastSpriteBatch.render(this); diff --git a/src/core/sprites/SpriteRenderer.js b/src/core/sprites/SpriteRenderer.js deleted file mode 100644 index fd9a802..0000000 --- a/src/core/sprites/SpriteRenderer.js +++ /dev/null @@ -1,537 +0,0 @@ -var ObjectRenderer = require('../renderers/webgl/utils/ObjectRenderer'), - Shader = require('../renderers/webgl/shaders/Shader'), - SpriteShader = require('../renderers/webgl/shaders/SpriteShader'), - WebGLRenderer = require('../renderers/webgl/WebGLRenderer'); - math = require('../math'); - -/** - * @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.setupContext(); -} - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; -module.exports = 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 = sprite; - 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 - gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, this.renderer.currentRenderTarget.projectionMatrix.toArray(true)); - } - } - - 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; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js new file mode 100644 index 0000000..c006ba4 --- /dev/null +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -0,0 +1,533 @@ +var ObjectRenderer = require('../../renderers/webgl/utils/ObjectRenderer'), + Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLRenderer = require('../../renderers/webgl/WebGLRenderer'), + SpriteShader = require('./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) +{ + 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; + + this.setupContext(); +} + +SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); +SpriteRenderer.prototype.constructor = SpriteRenderer; +module.exports = SpriteRenderer; + +WebGLRenderer.registerPlugin('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(this.renderer.shaderManager); + + // 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(this.renderer.shaderManager, 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 + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, this.renderer.currentRenderTarget.projectionMatrix.toArray(true)); + } + } + + 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; +}; diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js new file mode 100644 index 0000000..41be102 --- /dev/null +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -0,0 +1,48 @@ +/** + * @class + * @private + */ +function WebGLGraphicsData(gl) { + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +} + +WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; +module.exports = WebGLGraphicsData; + +/** + * + */ +WebGLGraphicsData.prototype.reset = function () { + this.points = []; + this.indices = []; +}; + +/** + * + */ +WebGLGraphicsData.prototype.upload = function () { + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; diff --git a/src/core/index.js b/src/core/index.js index 6f744e9..1d0a398 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -23,12 +23,12 @@ Sprite: require('./sprites/Sprite'), SpriteBatch: require('./sprites/SpriteBatch'), - SpriteRenderer: require('./sprites/SpriteRenderer'), + SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), // primitives - Graphics: require('./primitives/Graphics'), - GraphicsData: require('./primitives/GraphicsData'), - GraphicsRenderer: require('./primitives/GraphicsRenderer'), + Graphics: require('./graphics/Graphics'), + GraphicsData: require('./graphics/GraphicsData'), + GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,10 +42,8 @@ CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), // renderers - webgl - WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), - // WebGLGraphics: require('./renderers/webgl/utils/WebGLGraphics'), - - + WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), + Shader: require('./renderers/webgl/shaders/Shader'), /** * This helper function will automatically detect which renderer you should be using. @@ -103,7 +101,3 @@ return core.autoDetectRenderer(width, height, options, isAndroid); } }; - -// Adding here for now.. sure there is a better place! -core.WebGLRenderer.registerObjectRenderer('sprite', core.SpriteRenderer); -core.WebGLRenderer.registerObjectRenderer('graphics', core.GraphicsRenderer); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index a086950..81de05c 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -268,6 +268,6 @@ matrix.ty = this.ty; return matrix; -} +}; Matrix.IDENTITY = new Matrix(); diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js deleted file mode 100644 index 8db9b45..0000000 --- a/src/core/primitives/Graphics.js +++ /dev/null @@ -1,1116 +0,0 @@ -var DisplayObjectContainer = require('../display/DisplayObjectContainer'), - //Sprite = require('../display/Sprite'), - Texture = require('../textures/Texture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), - // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), - GraphicsData = require('./GraphicsData'), - math = require('../math'), - CONST = require('../const'); - -/** - * The Graphics class contains methods used to draw primitive shapes such as lines, circles and - * rectangles to the display, and color and fill them. - * - * @class - * @extends DisplayObjectContainer - * @namespace PIXI - */ -function Graphics() -{ - DisplayObjectContainer.call(this); - - this.renderable = true; - - /** - * The alpha value used when filling the Graphics object. - * - * @member {number} - * @default 1 - */ - this.fillAlpha = 1; - - /** - * The width (thickness) of any lines drawn. - * - * @member {number} - * @default 0 - */ - this.lineWidth = 0; - - /** - * The color of any lines drawn. - * - * @member {string} - * @default 0 - */ - this.lineColor = 0; - - /** - * Graphics data - * - * @member {GraphicsData[]} - * @private - */ - this.graphicsData = []; - - /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * Current path - * - * @member {GraphicsData} - * @private - */ - this.currentPath = null; - - /** - * Array containing some WebGL-related properties used by the WebGL renderer. - * - * @member {object} - * @private - */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; - - /** - * Whether this shape is being used as a mask. - * - * @member {boolean} - */ - this.isMask = false; - - /** - * The bounds' padding used for bounds calculation. - * - * @member {number} - */ - this.boundsPadding = 0; - - /** - * A cache of the local bounds to prevent recalculation. - * - * @member {Rectangle} - * @private - */ - this._localBounds = new math.Rectangle(0,0,1,1); - - /** - * Used to detect if the graphics object has changed. If this is set to true then the graphics - * object will be recalculated. - * - * @member {boolean} - * @private - */ - this.dirty = true; - - /** - * Used to detect if the WebGL graphics object has changed. If this is set to true then the - * graphics object will be recalculated. - * - * @member {boolean} - * @private - */ - this.glDirty = false; - - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; -} - -// constructor -Graphics.prototype = Object.create(DisplayObjectContainer.prototype); -Graphics.prototype.constructor = Graphics; -module.exports = Graphics; - -Object.defineProperties(Graphics.prototype, { - /** - * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. - * This is useful if your graphics element does not change often, as it will speed up the rendering - * of the object in exchange for taking up texture memory. It is also useful if you need the graphics - * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if - * you are constantly redrawing the graphics element. - * - * @member {boolean} - * @memberof Graphics# - * @default false - * @private - */ - cacheAsBitmap: { - get: function () - { - return this._cacheAsBitmap; - }, - set: function (value) - { - this._cacheAsBitmap = value; - - if (this._cacheAsBitmap) - { - this._generateCachedSprite(); - } - else - { - this.destroyCachedSprite(); - this.dirty = true; - } - } - } -}); - -/** - * Creates a new Graphics object with the same values as this one. - * - * @return {Graphics} - */ -GraphicsData.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = this.dirty; - clone.glDirty = this.glDirty; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData.clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {Graphics} - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (arguments.length < 3) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); - } - else - { - // otherwise its empty so lets just set the line properties - this.currentPath.lineWidth = this.lineWidth; - this.currentPath.lineColor = this.lineColor; - this.currentPath.lineAlpha = this.lineAlpha; - } - } - - return this; -}; - -/** - * Moves the current drawing position to x, y. - * - * @param x {number} the X coordinate to move to - * @param y {number} the Y coordinate to move to - * @return {Graphics} - */ -Graphics.prototype.moveTo = function (x, y) -{ - this.drawShape(new math.Polygon([x,y])); - - return this; -}; - -/** - * Draws a line using the current line style from the current drawing position to (x, y); - * The current drawing position is then set to (x, y). - * - * @param x {number} the X coordinate to draw to - * @param y {number} the Y coordinate to draw to - * @return {Graphics} - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a quadratic bezier curve and then draws it. - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @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 {Graphics} - */ -Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var xa, - ya, - n = 20, - points = this.currentPath.shape.points; - - if (points.length === 0) - { - this.moveTo(0, 0); - } - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - for (var i = 1; i <= n; ++i) - { - j = i / n; - - xa = fromX + ( (cpX - fromX) * j ); - ya = fromY + ( (cpY - fromY) * j ); - - points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), - ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); - } - - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a bezier curve and then draws it. - * - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param cpX2 {number} Second Control point x - * @param cpY2 {number} Second Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {Graphics} - */ -Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var n = 20, - dt, - dt2, - dt3, - t2, - t3, - points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - - for (var i = 1; i <= n; ++i) - { - j = i / n; - - dt = (1 - j); - dt2 = dt * dt; - dt3 = dt2 * dt; - - t2 = j * j; - t3 = t2 * j; - - points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, - dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); - } - - this.dirty = true; - - return this; -}; - -/** - * The arcTo() method creates an arc/curve between two tangents on the canvas. - * - * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! - * - * @param x1 {number} The x-coordinate of the beginning of the arc - * @param y1 {number} The y-coordinate of the beginning of the arc - * @param x2 {number} The x-coordinate of the end of the arc - * @param y2 {number} The y-coordinate of the end of the arc - * @param radius {number} The radius of the arc - * @return {Graphics} - */ -Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points.push(x1, y1); - } - } - else - { - this.moveTo(x1, y1); - } - - var points = this.currentPath.shape.points, - fromX = points[points.length-2], - fromY = points[points.length-1], - a1 = fromY - y1, - b1 = fromX - x1, - a2 = y2 - y1, - b2 = x2 - x1, - mm = Math.abs(a1 * b2 - b1 * a2); - - if (mm < 1.0e-8 || radius === 0) - { - if (points[points.length-2] !== x1 || points[points.length-1] !== y1) - { - points.push(x1, y1); - } - } - else - { - var dd = a1 * a1 + b1 * b1, - cc = a2 * a2 + b2 * b2, - tt = a1 * a2 + b1 * b2, - k1 = radius * Math.sqrt(dd) / mm, - k2 = radius * Math.sqrt(cc) / mm, - j1 = k1 * tt / dd, - j2 = k2 * tt / cc, - cx = k1 * b2 + k2 * b1, - cy = k1 * a2 + k2 * a1, - px = b1 * (k2 + j1), - py = a1 * (k2 + j1), - qx = b2 * (k1 + j2), - qy = a2 * (k1 + j2), - startAngle = Math.atan2(py - cy, px - cx), - endAngle = Math.atan2(qy - cy, qx - cx); - - this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); - } - - this.dirty = true; - - return this; -}; - -/** - * The arc method creates an arc/curve (used to create circles, or parts of circles). - * - * @param cx {number} The x-coordinate of the center of the circle - * @param cy {number} The y-coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) - * @param endAngle {number} The ending angle, in radians - * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {Graphics} - */ -Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - var points; - - // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... - if (this.currentPath) - { - points = this.currentPath.shape.points; - - if (points.length === 0) - { - points.push(startX, startY); - } - else if (points[points.length-2] !== startX || points[points.length-1] !== startY) - { - points.push(startX, startY); - } - } - else - { - this.moveTo(startX, startY); - points = this.currentPath.shape.points; - } - - if (startAngle === endAngle) - { - return this; - } - - if (!anticlockwise && endAngle <= startAngle) - { - endAngle += Math.PI * 2; - } - else if (anticlockwise && startAngle <= endAngle) - { - startAngle += Math.PI * 2; - } - - var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); - var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; - - if (sweep === 0) - { - return this; - } - - var theta = sweep/(segs*2); - var theta2 = theta*2; - - var cTheta = Math.cos(theta); - var sTheta = Math.sin(theta); - - var segMinus = segs - 1; - - var remainder = ( segMinus % 1 ) / segMinus; - - for (var i = 0; i <= segMinus; ++i) - { - var real = i + remainder * i; - var angle = ((theta) + startAngle + (theta2 * real)); - - var c = Math.cos(angle); - var s = -Math.sin(angle); - - points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, - ( (cTheta * -s) + (sTheta * c) ) * radius + cy); - } - - this.dirty = true; - - return this; -}; - -/** - * Specifies a simple one-color fill that subsequent calls to other Graphics methods - * (such as lineTo() or drawCircle()) use when drawing. - * - * @param color {number} the color of the fill - * @param alpha {number} the alpha of the fill - * @return {Graphics} - */ -Graphics.prototype.beginFill = function (color, alpha) -{ - this.filling = true; - this.fillColor = color || 0; - this.fillAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length <= 2) - { - this.currentPath.fill = this.filling; - this.currentPath.fillColor = this.fillColor; - this.currentPath.fillAlpha = this.fillAlpha; - } - } - return this; -}; - -/** - * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. - * - * @return {Graphics} - */ -Graphics.prototype.endFill = function () -{ - this.filling = false; - this.fillColor = null; - this.fillAlpha = 1; - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @return {Graphics} - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {Graphics} - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {Graphics} - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {Array} The path data used to construct the polygon. - * @return {Graphics} - */ -Graphics.prototype.drawPolygon = function (path) -{ - if (!(path instanceof Array)) - { - path = Array.prototype.slice.call(arguments); - } - - this.drawShape(new math.Polygon(path)); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {Graphics} - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty = true; - this.clearDirty = true; - this.graphicsData = []; - - return this; -}; - -/** - * Useful function that returns a texture of the graphics object that can then be used to create sprites - * This can be quite useful if your geometry is complicated and needs to be reused multiple times. - * - * @param resolution {number} The resolution of the texture being generated - * @param scaleMode {number} Should be one of the scaleMode consts - * @return {Texture} a texture of the graphics object - */ -Graphics.prototype.generateTexture = function (resolution, scaleMode) -{ - resolution = resolution || 1; - - var bounds = this.getBounds(); - - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); - - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - canvasBuffer.context.scale(resolution, resolution); - - canvasBuffer.context.translate(-bounds.x,-bounds.y); - - CanvasGraphics.renderGraphics(this, canvasBuffer.context); - - return texture; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (this.isMask === true) - { - return; - } - - // this code may still be needed so leaving for now.. - // - /* - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture on the gpu too! - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.worldAlpha = this.worldAlpha; - - Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); - - return; - } - - */ - - if (this.glDirty) - { - this.dirty = true; - this.glDirty = false; - } - - renderer.setObjectRenderer(renderer.objectRenderers.graphics); - renderer.objectRenderers.graphics.render(this); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} - * @private - */ -Graphics.prototype.renderCanvas = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.alpha <= 0 || this.isMask === true) - { - return; - } - - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.alpha = this.alpha; - - Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); - - return; - } - else - { - var context = renderer.context; - var transform = this.worldTransform; - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - var resolution = renderer.resolution; - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } - } -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @return {Rectangle} the rectangular bounding area - */ -Graphics.prototype.getBounds = function (matrix) -{ - // return an empty object if the item is a mask! - if (this.isMask) - { - return math.Rectangle.EMPTY; - } - - if (this.dirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.dirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - return this._bounds; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; - - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; -}; - -/** - * Generates the cached sprite when the sprite has cacheAsBitmap = true - * - * @private - */ -Graphics.prototype._generateCachedSprite = function () -{ - var bounds = this.getLocalBounds(); - - if (!this._cachedSprite) - { - var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); - var texture = Texture.fromCanvas(canvasBuffer.canvas); - - this._cachedSprite = new Sprite(texture); - this._cachedSprite.buffer = canvasBuffer; - - this._cachedSprite.worldTransform = this.worldTransform; - } - else - { - this._cachedSprite.buffer.resize(bounds.width, bounds.height); - } - - // leverage the anchor to account for the offset of the element - this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); - this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); - - // this._cachedSprite.buffer.context.save(); - this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); - - // make sure we set the alpha of the graphics to 1 for the render.. - this.worldAlpha = 1; - - // now render the graphic.. - CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); - - this._cachedSprite.alpha = this.alpha; -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Graphics.prototype.updateCachedSpriteTexture = function () -{ - var cachedSprite = this._cachedSprite; - var texture = cachedSprite.texture; - var canvas = cachedSprite.buffer.canvas; - - texture.baseTexture.width = canvas.width; - texture.baseTexture.height = canvas.height; - texture.crop.width = texture.frame.width = canvas.width; - texture.crop.height = texture.frame.height = canvas.height; - - cachedSprite._width = canvas.width; - cachedSprite._height = canvas.height; - - // update the dirty base textures - texture.baseTexture.dirty(); -}; - -/** - * Destroys a previous cached sprite. - * - */ -Graphics.prototype.destroyCachedSprite = function () -{ - this._cachedSprite.texture.destroy(true); - - // let the gc collect the unused sprite - // TODO could be object pooled! - this._cachedSprite = null; -}; - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. - * @return {GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = this.filling; - this.currentPath = data; - } - - this.dirty = true; - - return data; -}; diff --git a/src/core/primitives/GraphicsData.js b/src/core/primitives/GraphicsData.js deleted file mode 100644 index 788f6f2..0000000 --- a/src/core/primitives/GraphicsData.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * A GraphicsData object. - * - * @class - * @namespace PIXI - */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - this.lineWidth = lineWidth; - this.lineColor = lineColor; - this.lineAlpha = lineAlpha; - this._lineTint = lineColor; - - this.fillColor = fillColor; - this.fillAlpha = fillAlpha; - this._fillTint = fillColor; - this.fill = fill; - - this.shape = shape; - this.type = shape.type; -} - -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {GraphicsData} - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js deleted file mode 100644 index 1a508f9..0000000 --- a/src/core/primitives/GraphicsRenderer.js +++ /dev/null @@ -1,874 +0,0 @@ -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; - -/** - * 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 ); - -// var matrix = graphics.worldTransform.clone(); -// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); -// matrix.append(graphics.worldTransform); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.maskManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.maskManager.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.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); - - 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/primitives/WebGLGraphicsData.js b/src/core/primitives/WebGLGraphicsData.js deleted file mode 100755 index 41be102..0000000 --- a/src/core/primitives/WebGLGraphicsData.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @class - * @private - */ -function WebGLGraphicsData(gl) { - this.gl = gl; - - //TODO does this need to be split before uploding?? - this.color = [0,0,0]; // color split! - this.points = []; - this.indices = []; - this.buffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - this.mode = 1; - this.alpha = 1; - this.dirty = true; -} - -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; -module.exports = WebGLGraphicsData; - -/** - * - */ -WebGLGraphicsData.prototype.reset = function () { - this.points = []; - this.indices = []; -}; - -/** - * - */ -WebGLGraphicsData.prototype.upload = function () { - var gl = this.gl; - -// this.lastIndex = graphics.graphicsData.length; - this.glPoints = new Float32Array(this.points); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); - - this.glIndicies = new Uint16Array(this.indices); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); - - this.dirty = false; -}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6c1fb7d..4e82baa 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -174,6 +174,9 @@ // time to create the render managers! each one focuses on managing a state in webGL + // initialize the context so it is ready for the managers. + this._initContext(); + /** * Deals with managing the shader programs and their attribs * @member {WebGLShaderManager} @@ -204,17 +207,11 @@ */ this.blendModeManager = new WebGLBlendModeManager(this); - - this.blendModes = null; this._boundUpdateTexture = this.updateTexture.bind(this); this._boundDestroyTexture = this.destroyTexture.bind(this); - - - // time init the context.. - this._initContext(); this.currentRenderTarget = this.renderTarget; @@ -228,27 +225,18 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - /** - * Manages the renderer of specific objects. - * - * @member {object} - */ - this.objectRenderers = {}; - this.currentRenderer = new ObjectRenderer(); - - // create an instance of each registered object renderer - for (var o in WebGLRenderer._objectRenderers) { - this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); - } - + this.initPlugins(); } // constructor WebGLRenderer.prototype.constructor = WebGLRenderer; module.exports = WebGLRenderer; +WebGLRenderer.glContextId = 0; + +utils.pluginTarget.mixin(WebGLRenderer); utils.eventTarget.mixin(WebGLRenderer.prototype); Object.defineProperties(WebGLRenderer.prototype, { @@ -297,11 +285,9 @@ gl.enable(gl.BLEND); this.renderTarget = new RenderTarget(this.gl, this.width, this.height, null, true); - this.emit('context', gl); - // now resize and we are good to go! this.resize(this.width, this.height); }; @@ -349,7 +335,7 @@ gl.clear(gl.COLOR_BUFFER_BIT); } - this.renderDisplayObject(object, this.renderTarget)//this.projection); + this.renderDisplayObject(object, this.renderTarget);//this.projection); }; /** @@ -536,12 +522,7 @@ this.maskManager.destroy(); this.filterManager.destroy(); - for (var o in this.objectRenderers) { - this.objectRenderers[o].destroy(); - this.objectRenderers[o] = null; - } - - this.objectRenderers = null; + this.destroyPlugins(); // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -611,11 +592,3 @@ this.blendModes[CONST.blendModes.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; } }; - -WebGLRenderer.glContextId = 0; -WebGLRenderer._objectRenderers = {}; - -WebGLRenderer.registerObjectRenderer = function (name, ctor) { - WebGLRenderer._objectRenderers[name] = ctor; -}; - diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js index 87c99f0..d6ed3c6 100644 --- a/src/core/renderers/webgl/managers/WebGLFilterManager.js +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -51,7 +51,7 @@ */ WebGLFilterManager.prototype.begin = function (buffer) { - this.defaultShader = this.renderer.shaderManager.defaultShader; + this.defaultShader = this.renderer.shaderManager.plugins.defaultShader; this.width = this.renderer.projection.x * 2; this.height = -this.renderer.projection.y * 2; @@ -385,7 +385,7 @@ if (!shader) { - shader = new Shader(gl); + shader = new Shader(this.renderer.shaderManager); shader.fragmentSrc = filter.fragmentSrc; shader.uniforms = filter.uniforms; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js index 6a1f782..d6a11c1 100644 --- a/src/core/renderers/webgl/managers/WebGLMaskManager.js +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -126,11 +126,11 @@ // bind the graphics object.. var projection = this.renderer.projection, offset = this.renderer.offset, - shader;// = this.renderer.shaderManager.primitiveShader; + shader;// = this.renderer.shaderManager.plugins.primitiveShader; if (webGLData.mode === 1) { - shader = this.renderer.shaderManager.complexPrimitiveShader; + shader = this.renderer.shaderManager.plugins.complexPrimitiveShader; this.renderer.shaderManager.setShader(shader); @@ -156,7 +156,7 @@ else { //this.renderer.shaderManager.activatePrimitiveShader(); - shader = this.renderer.shaderManager.primitiveShader; + shader = this.renderer.shaderManager.plugins.primitiveShader; this.renderer.shaderManager.setShader( shader ); gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); @@ -292,11 +292,11 @@ */ WebGLMaskManager.prototype.pushMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); if (maskData.dirty) { - this.renderer.objectRenderers.graphics.updateGraphics(maskData, this.renderer.gl); + this.renderer.plugins.graphics.updateGraphics(maskData, this.renderer.gl); } if (!maskData._webGL[this.renderer.gl.id].data.length) @@ -314,7 +314,7 @@ */ WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); this.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); }; diff --git a/src/core/renderers/webgl/managers/WebGLShaderManager.js b/src/core/renderers/webgl/managers/WebGLShaderManager.js index 90c6477..9ebb58e 100644 --- a/src/core/renderers/webgl/managers/WebGLShaderManager.js +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -1,9 +1,5 @@ var WebGLManager = require('./WebGLManager'), - PrimitiveShader = require('../shaders/PrimitiveShader'), - ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), - Shader = require('../shaders/Shader'), - FastShader = require('../shaders/FastShader'), - StripShader = require('../shaders/StripShader'); + utils = require('../../../utils'); /** * @class @@ -51,41 +47,16 @@ */ this.currentShader = null; - // this shader is used for rendering primitives - this.primitiveShader = null; - - // this shader is used for rendering triangle strips - this.complexPrimitiveShader = null; - - // this shader is used for the default sprite rendering - this.defaultShader = null; - - // this shader is used for the fast sprite rendering - this.fastShader = null; - - // the next one is used for rendering triangle strips - this.stripShader = null; + this.initPlugins(); // listen for context and update necessary shaders var self = this; - this.renderer.on('context', function (event) + this.renderer.on('context', function () { - var gl = event.data; - - // this shader is used for rendering primitives - self.primitiveShader = new PrimitiveShader(gl); - - // this shader is used for rendering triangle strips - self.complexPrimitiveShader = new ComplexPrimitiveShader(gl); - - // this shader is used for the default sprite rendering - self.defaultShader = new Shader(gl); - - // this shader is used for the fast sprite rendering - self.fastShader = new FastShader(gl); - - // the next one is used for rendering triangle strips - self.stripShader = new StripShader(gl); + for (var o in this.plugins) + { + this.plugins[o] = new (this.plugins[o].constructor)(self); + } self.setShader(self.defaultShader); }); @@ -95,6 +66,8 @@ WebGLShaderManager.prototype.constructor = WebGLShaderManager; module.exports = WebGLShaderManager; +utils.pluginTarget.mixin(WebGLShaderManager); + /** * Takes the attributes given in parameters. * @@ -164,24 +137,11 @@ */ WebGLShaderManager.prototype.destroy = function () { + this.destroyPlugins(); + this.attribState = null; this.tempAttribState = null; - this.primitiveShader.destroy(); - this.primitiveShader = null; - - this.complexPrimitiveShader.destroy(); - this.complexPrimitiveShader = null; - - this.defaultShader.destroy(); - this.defaultShader = null; - - this.fastShader.destroy(); - this.fastShader = null; - - this.stripShader.destroy(); - this.stripShader = null; - this.renderer = null; }; diff --git a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js b/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js deleted file mode 100644 index f8a07da..0000000 --- a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js +++ /dev/null @@ -1,53 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function ComplexPrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform vec3 tint;', - 'uniform float alpha;', - 'uniform vec3 color;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - color: { type: '3f', value: [0,0,0] }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); -ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; -module.exports = ComplexPrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/FastShader.js b/src/core/renderers/webgl/shaders/FastShader.js index d198f10..c80d040 100644 --- a/src/core/renderers/webgl/shaders/FastShader.js +++ b/src/core/renderers/webgl/shaders/FastShader.js @@ -1,15 +1,16 @@ -var Shader = require('./Shader'); +var Shader = require('./Shader'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @extends Shader * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. */ -function FastShader(gl) +function FastShader(shaderManager) { Shader.call(this, - gl, + shaderManager, // vertex shader [ 'attribute vec2 aVertexPosition;', @@ -70,3 +71,5 @@ FastShader.prototype = Object.create(Shader.prototype); FastShader.prototype.constructor = FastShader; module.exports = FastShader; + +WebGLShaderManager.registerPlugin('fastShader', FastShader); diff --git a/src/core/renderers/webgl/shaders/PrimitiveShader.js b/src/core/renderers/webgl/shaders/PrimitiveShader.js deleted file mode 100644 index 753c475..0000000 --- a/src/core/renderers/webgl/shaders/PrimitiveShader.js +++ /dev/null @@ -1,55 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform float alpha;', - 'uniform float flipY;', - 'uniform vec3 tint;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aColor:0 - } - ); -} - -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; -module.exports = PrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/Shader.js b/src/core/renderers/webgl/shaders/Shader.js index d065a76..c60c5f9 100644 --- a/src/core/renderers/webgl/shaders/Shader.js +++ b/src/core/renderers/webgl/shaders/Shader.js @@ -1,12 +1,16 @@ -var utils = require('../../../utils'); +var utils = require('../../../utils'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @namespace PIXI - * @param [fragmentSrc] {string} The source of the fragment shader. + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. * @param [vertexSrc] {string} The source of the vertex shader. + * @param [fragmentSrc] {string} The source of the fragment shader. + * @param customUniforms {object} Custom uniforms to use to augment the built-in ones. + * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Shader(gl, vertexSrc, fragmentSrc, customUniforms, customAttributes) +function Shader(shaderManager, vertexSrc, fragmentSrc, customUniforms, customAttributes) { /** * @member {number} @@ -18,7 +22,7 @@ * @member {WebGLContext} * @readonly */ - this.gl = gl; + this.gl = shaderManager.renderer.gl; /** * The WebGL program. @@ -31,8 +35,8 @@ uSampler: { type: 'sampler2D', value: 0 }, projectionVector: { type: '2f', value: { x: 0, y: 0 } }, offsetVector: { type: '2f', value: { x: 0, y: 0 } }, - projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, - 0, 1, 0, + projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, + 0, 1, 0, 0, 0, 1) }, dimensions: { type: '4f', value: new Float32Array(4) } }; @@ -103,6 +107,8 @@ Shader.prototype.constructor = Shader; module.exports = Shader; +WebGLShaderManager.registerPlugin('defaultShader', Shader); + Shader.prototype.init = function () { this.compile(); @@ -506,4 +512,4 @@ } return shader; -}; \ No newline at end of file +}; diff --git a/src/core/renderers/webgl/shaders/SpriteShader.js b/src/core/renderers/webgl/shaders/SpriteShader.js deleted file mode 100644 index 1805b0a..0000000 --- a/src/core/renderers/webgl/shaders/SpriteShader.js +++ /dev/null @@ -1,42 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function SpriteShader(gl) -{ - Shader.call(this, - gl, - null, - // fragment shader - [ - 'precision lowp float;', - - 'varying vec2 vTextureCoord;', - 'varying vec4 vColor;', - - 'uniform sampler2D uSampler;', - - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aTextureCoord: 0, - aColor: 0 - } - ); -} - -SpriteShader.prototype = Object.create(Shader.prototype); -SpriteShader.prototype.constructor = SpriteShader; -module.exports = SpriteShader; diff --git a/src/core/renderers/webgl/shaders/StripShader.js b/src/core/renderers/webgl/shaders/StripShader.js deleted file mode 100644 index f487381..0000000 --- a/src/core/renderers/webgl/shaders/StripShader.js +++ /dev/null @@ -1,54 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function StripShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - // 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform vec2 projectionVector;', - 'uniform vec2 offsetVector;', - - 'varying vec2 vTextureCoord;', - - 'void main(void){', - ' vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);', - ' v -= offsetVector.xyx;', - ' gl_Position = vec4( v.x / projectionVector.x -1.0, v.y / -projectionVector.y + 1.0 , 0.0, 1.0);', - ' vTextureCoord = aTextureCoord;', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'uniform float alpha;', - 'uniform sampler2D uSampler;', - - 'varying vec2 vTextureCoord;', - - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y)) * alpha;', - '}' - ].join('\n'), - // custom uniforms - { - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -StripShader.prototype = Object.create(Shader.prototype); -StripShader.prototype.constructor = StripShader; -module.exports = StripShader; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index 0a34e55..43e2d00 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -33,7 +33,7 @@ * @property texture * @type Any */ - this.texture = null + this.texture = null; this.width = 0; this.height = 0; @@ -56,11 +56,11 @@ this.frameBuffer = gl.createFramebuffer(); - /* + /* A frame buffer needs a target to render to.. create a texture and bind it attach it to the framebuffer.. */ - + this.texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, this.texture); @@ -70,13 +70,13 @@ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); 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); - + gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer ); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0); - - /* + + /* The stencil buffer is used for masking in pixi lets create one and then add attach it to the framebuffer.. */ @@ -86,7 +86,7 @@ } this.resize(width, height); - + }; RenderTarget.prototype.constructor = RenderTarget; @@ -94,21 +94,21 @@ /** * Clears the filter texture. -* +* * @method clear */ RenderTarget.prototype.clear = function() { var gl = this.gl; - + gl.clearColor(0,0,0, 0); gl.clear(gl.COLOR_BUFFER_BIT); }; RenderTarget.prototype.activate = function() { - this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer ); -} + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer); +}; /** * Resizes the texture to the specified width and height @@ -119,25 +119,25 @@ */ RenderTarget.prototype.resize = function(width, height) { - width = width | 0; - height = height | 0; - - if(this.width === width && this.height === height) return; + if (this.width === width && this.height === height) { + return; + } this.width = width; this.height = height; + this.projectionMatrix = new math.Matrix(); - - if(!this.root) + + if (!this.root) { var gl = this.gl; this.projectionMatrix.a = 1/width*2; - this.projectionMatrix.d = 1/height*2; + this.projectionMatrix.d = -1/height*2; this.projectionMatrix.tx = -1; - this.projectionMatrix.ty = -1; + this.projectionMatrix.ty = 1; gl.bindTexture(gl.TEXTURE_2D, this.texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width , height , 0, gl.RGBA, gl.UNSIGNED_BYTE, null); @@ -157,7 +157,7 @@ /** * Destroys the filter texture. -* +* * @method destroy */ RenderTarget.prototype.destroy = function() diff --git a/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js b/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js index a63f4d7..3f37b7d 100644 --- a/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js +++ b/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js @@ -183,7 +183,7 @@ */ WebGLFastSpriteBatch.prototype.begin = function (spriteBatch) { - this.shader = this.renderer.shaderManager.fastShader; + this.shader = this.renderer.shaderManager.plugins.fastShader; this.matrix = spriteBatch.worldTransform.toArray(true); diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index bf8db28..2dc47ff 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -201,8 +201,8 @@ Sprite.prototype._renderWebGL = function (renderer) { - renderer.setObjectRenderer(renderer.objectRenderers.sprite); - renderer.objectRenderers.sprite.render(this); + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); }; /** diff --git a/src/core/sprites/SpriteBatch.js b/src/core/sprites/SpriteBatch.js index 63cebc0..4fe33e4 100644 --- a/src/core/sprites/SpriteBatch.js +++ b/src/core/sprites/SpriteBatch.js @@ -61,7 +61,7 @@ renderer.spriteBatch.stop(); - renderer.shaderManager.setShader(renderer.shaderManager.fastShader); + renderer.shaderManager.setShader(renderer.shaderManager.plugins.fastShader); renderer.fastSpriteBatch.begin(this); renderer.fastSpriteBatch.render(this); diff --git a/src/core/sprites/SpriteRenderer.js b/src/core/sprites/SpriteRenderer.js deleted file mode 100644 index fd9a802..0000000 --- a/src/core/sprites/SpriteRenderer.js +++ /dev/null @@ -1,537 +0,0 @@ -var ObjectRenderer = require('../renderers/webgl/utils/ObjectRenderer'), - Shader = require('../renderers/webgl/shaders/Shader'), - SpriteShader = require('../renderers/webgl/shaders/SpriteShader'), - WebGLRenderer = require('../renderers/webgl/WebGLRenderer'); - math = require('../math'); - -/** - * @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.setupContext(); -} - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; -module.exports = 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 = sprite; - 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 - gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, this.renderer.currentRenderTarget.projectionMatrix.toArray(true)); - } - } - - 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; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js new file mode 100644 index 0000000..c006ba4 --- /dev/null +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -0,0 +1,533 @@ +var ObjectRenderer = require('../../renderers/webgl/utils/ObjectRenderer'), + Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLRenderer = require('../../renderers/webgl/WebGLRenderer'), + SpriteShader = require('./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) +{ + 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; + + this.setupContext(); +} + +SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); +SpriteRenderer.prototype.constructor = SpriteRenderer; +module.exports = SpriteRenderer; + +WebGLRenderer.registerPlugin('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(this.renderer.shaderManager); + + // 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(this.renderer.shaderManager, 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 + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, this.renderer.currentRenderTarget.projectionMatrix.toArray(true)); + } + } + + 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; +}; diff --git a/src/core/sprites/webgl/SpriteShader.js b/src/core/sprites/webgl/SpriteShader.js new file mode 100644 index 0000000..3925e9f --- /dev/null +++ b/src/core/sprites/webgl/SpriteShader.js @@ -0,0 +1,45 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function SpriteShader(shaderManager) +{ + Shader.call(this, + shaderManager, + null, + // fragment shader + [ + 'precision lowp float;', + + 'varying vec2 vTextureCoord;', + 'varying vec4 vColor;', + + 'uniform sampler2D uSampler;', + + 'void main(void){', + ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aTextureCoord: 0, + aColor: 0 + } + ); +} + +SpriteShader.prototype = Object.create(Shader.prototype); +SpriteShader.prototype.constructor = SpriteShader; +module.exports = SpriteShader; + +WebGLShaderManager.registerPlugin('fastShader', SpriteShader); diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js new file mode 100644 index 0000000..41be102 --- /dev/null +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -0,0 +1,48 @@ +/** + * @class + * @private + */ +function WebGLGraphicsData(gl) { + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +} + +WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; +module.exports = WebGLGraphicsData; + +/** + * + */ +WebGLGraphicsData.prototype.reset = function () { + this.points = []; + this.indices = []; +}; + +/** + * + */ +WebGLGraphicsData.prototype.upload = function () { + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; diff --git a/src/core/index.js b/src/core/index.js index 6f744e9..1d0a398 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -23,12 +23,12 @@ Sprite: require('./sprites/Sprite'), SpriteBatch: require('./sprites/SpriteBatch'), - SpriteRenderer: require('./sprites/SpriteRenderer'), + SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), // primitives - Graphics: require('./primitives/Graphics'), - GraphicsData: require('./primitives/GraphicsData'), - GraphicsRenderer: require('./primitives/GraphicsRenderer'), + Graphics: require('./graphics/Graphics'), + GraphicsData: require('./graphics/GraphicsData'), + GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,10 +42,8 @@ CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), // renderers - webgl - WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), - // WebGLGraphics: require('./renderers/webgl/utils/WebGLGraphics'), - - + WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), + Shader: require('./renderers/webgl/shaders/Shader'), /** * This helper function will automatically detect which renderer you should be using. @@ -103,7 +101,3 @@ return core.autoDetectRenderer(width, height, options, isAndroid); } }; - -// Adding here for now.. sure there is a better place! -core.WebGLRenderer.registerObjectRenderer('sprite', core.SpriteRenderer); -core.WebGLRenderer.registerObjectRenderer('graphics', core.GraphicsRenderer); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index a086950..81de05c 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -268,6 +268,6 @@ matrix.ty = this.ty; return matrix; -} +}; Matrix.IDENTITY = new Matrix(); diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js deleted file mode 100644 index 8db9b45..0000000 --- a/src/core/primitives/Graphics.js +++ /dev/null @@ -1,1116 +0,0 @@ -var DisplayObjectContainer = require('../display/DisplayObjectContainer'), - //Sprite = require('../display/Sprite'), - Texture = require('../textures/Texture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), - // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), - GraphicsData = require('./GraphicsData'), - math = require('../math'), - CONST = require('../const'); - -/** - * The Graphics class contains methods used to draw primitive shapes such as lines, circles and - * rectangles to the display, and color and fill them. - * - * @class - * @extends DisplayObjectContainer - * @namespace PIXI - */ -function Graphics() -{ - DisplayObjectContainer.call(this); - - this.renderable = true; - - /** - * The alpha value used when filling the Graphics object. - * - * @member {number} - * @default 1 - */ - this.fillAlpha = 1; - - /** - * The width (thickness) of any lines drawn. - * - * @member {number} - * @default 0 - */ - this.lineWidth = 0; - - /** - * The color of any lines drawn. - * - * @member {string} - * @default 0 - */ - this.lineColor = 0; - - /** - * Graphics data - * - * @member {GraphicsData[]} - * @private - */ - this.graphicsData = []; - - /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * Current path - * - * @member {GraphicsData} - * @private - */ - this.currentPath = null; - - /** - * Array containing some WebGL-related properties used by the WebGL renderer. - * - * @member {object} - * @private - */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; - - /** - * Whether this shape is being used as a mask. - * - * @member {boolean} - */ - this.isMask = false; - - /** - * The bounds' padding used for bounds calculation. - * - * @member {number} - */ - this.boundsPadding = 0; - - /** - * A cache of the local bounds to prevent recalculation. - * - * @member {Rectangle} - * @private - */ - this._localBounds = new math.Rectangle(0,0,1,1); - - /** - * Used to detect if the graphics object has changed. If this is set to true then the graphics - * object will be recalculated. - * - * @member {boolean} - * @private - */ - this.dirty = true; - - /** - * Used to detect if the WebGL graphics object has changed. If this is set to true then the - * graphics object will be recalculated. - * - * @member {boolean} - * @private - */ - this.glDirty = false; - - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; -} - -// constructor -Graphics.prototype = Object.create(DisplayObjectContainer.prototype); -Graphics.prototype.constructor = Graphics; -module.exports = Graphics; - -Object.defineProperties(Graphics.prototype, { - /** - * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. - * This is useful if your graphics element does not change often, as it will speed up the rendering - * of the object in exchange for taking up texture memory. It is also useful if you need the graphics - * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if - * you are constantly redrawing the graphics element. - * - * @member {boolean} - * @memberof Graphics# - * @default false - * @private - */ - cacheAsBitmap: { - get: function () - { - return this._cacheAsBitmap; - }, - set: function (value) - { - this._cacheAsBitmap = value; - - if (this._cacheAsBitmap) - { - this._generateCachedSprite(); - } - else - { - this.destroyCachedSprite(); - this.dirty = true; - } - } - } -}); - -/** - * Creates a new Graphics object with the same values as this one. - * - * @return {Graphics} - */ -GraphicsData.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = this.dirty; - clone.glDirty = this.glDirty; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData.clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {Graphics} - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (arguments.length < 3) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); - } - else - { - // otherwise its empty so lets just set the line properties - this.currentPath.lineWidth = this.lineWidth; - this.currentPath.lineColor = this.lineColor; - this.currentPath.lineAlpha = this.lineAlpha; - } - } - - return this; -}; - -/** - * Moves the current drawing position to x, y. - * - * @param x {number} the X coordinate to move to - * @param y {number} the Y coordinate to move to - * @return {Graphics} - */ -Graphics.prototype.moveTo = function (x, y) -{ - this.drawShape(new math.Polygon([x,y])); - - return this; -}; - -/** - * Draws a line using the current line style from the current drawing position to (x, y); - * The current drawing position is then set to (x, y). - * - * @param x {number} the X coordinate to draw to - * @param y {number} the Y coordinate to draw to - * @return {Graphics} - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a quadratic bezier curve and then draws it. - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @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 {Graphics} - */ -Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var xa, - ya, - n = 20, - points = this.currentPath.shape.points; - - if (points.length === 0) - { - this.moveTo(0, 0); - } - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - for (var i = 1; i <= n; ++i) - { - j = i / n; - - xa = fromX + ( (cpX - fromX) * j ); - ya = fromY + ( (cpY - fromY) * j ); - - points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), - ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); - } - - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a bezier curve and then draws it. - * - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param cpX2 {number} Second Control point x - * @param cpY2 {number} Second Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {Graphics} - */ -Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var n = 20, - dt, - dt2, - dt3, - t2, - t3, - points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - - for (var i = 1; i <= n; ++i) - { - j = i / n; - - dt = (1 - j); - dt2 = dt * dt; - dt3 = dt2 * dt; - - t2 = j * j; - t3 = t2 * j; - - points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, - dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); - } - - this.dirty = true; - - return this; -}; - -/** - * The arcTo() method creates an arc/curve between two tangents on the canvas. - * - * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! - * - * @param x1 {number} The x-coordinate of the beginning of the arc - * @param y1 {number} The y-coordinate of the beginning of the arc - * @param x2 {number} The x-coordinate of the end of the arc - * @param y2 {number} The y-coordinate of the end of the arc - * @param radius {number} The radius of the arc - * @return {Graphics} - */ -Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points.push(x1, y1); - } - } - else - { - this.moveTo(x1, y1); - } - - var points = this.currentPath.shape.points, - fromX = points[points.length-2], - fromY = points[points.length-1], - a1 = fromY - y1, - b1 = fromX - x1, - a2 = y2 - y1, - b2 = x2 - x1, - mm = Math.abs(a1 * b2 - b1 * a2); - - if (mm < 1.0e-8 || radius === 0) - { - if (points[points.length-2] !== x1 || points[points.length-1] !== y1) - { - points.push(x1, y1); - } - } - else - { - var dd = a1 * a1 + b1 * b1, - cc = a2 * a2 + b2 * b2, - tt = a1 * a2 + b1 * b2, - k1 = radius * Math.sqrt(dd) / mm, - k2 = radius * Math.sqrt(cc) / mm, - j1 = k1 * tt / dd, - j2 = k2 * tt / cc, - cx = k1 * b2 + k2 * b1, - cy = k1 * a2 + k2 * a1, - px = b1 * (k2 + j1), - py = a1 * (k2 + j1), - qx = b2 * (k1 + j2), - qy = a2 * (k1 + j2), - startAngle = Math.atan2(py - cy, px - cx), - endAngle = Math.atan2(qy - cy, qx - cx); - - this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); - } - - this.dirty = true; - - return this; -}; - -/** - * The arc method creates an arc/curve (used to create circles, or parts of circles). - * - * @param cx {number} The x-coordinate of the center of the circle - * @param cy {number} The y-coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) - * @param endAngle {number} The ending angle, in radians - * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {Graphics} - */ -Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - var points; - - // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... - if (this.currentPath) - { - points = this.currentPath.shape.points; - - if (points.length === 0) - { - points.push(startX, startY); - } - else if (points[points.length-2] !== startX || points[points.length-1] !== startY) - { - points.push(startX, startY); - } - } - else - { - this.moveTo(startX, startY); - points = this.currentPath.shape.points; - } - - if (startAngle === endAngle) - { - return this; - } - - if (!anticlockwise && endAngle <= startAngle) - { - endAngle += Math.PI * 2; - } - else if (anticlockwise && startAngle <= endAngle) - { - startAngle += Math.PI * 2; - } - - var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); - var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; - - if (sweep === 0) - { - return this; - } - - var theta = sweep/(segs*2); - var theta2 = theta*2; - - var cTheta = Math.cos(theta); - var sTheta = Math.sin(theta); - - var segMinus = segs - 1; - - var remainder = ( segMinus % 1 ) / segMinus; - - for (var i = 0; i <= segMinus; ++i) - { - var real = i + remainder * i; - var angle = ((theta) + startAngle + (theta2 * real)); - - var c = Math.cos(angle); - var s = -Math.sin(angle); - - points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, - ( (cTheta * -s) + (sTheta * c) ) * radius + cy); - } - - this.dirty = true; - - return this; -}; - -/** - * Specifies a simple one-color fill that subsequent calls to other Graphics methods - * (such as lineTo() or drawCircle()) use when drawing. - * - * @param color {number} the color of the fill - * @param alpha {number} the alpha of the fill - * @return {Graphics} - */ -Graphics.prototype.beginFill = function (color, alpha) -{ - this.filling = true; - this.fillColor = color || 0; - this.fillAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length <= 2) - { - this.currentPath.fill = this.filling; - this.currentPath.fillColor = this.fillColor; - this.currentPath.fillAlpha = this.fillAlpha; - } - } - return this; -}; - -/** - * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. - * - * @return {Graphics} - */ -Graphics.prototype.endFill = function () -{ - this.filling = false; - this.fillColor = null; - this.fillAlpha = 1; - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @return {Graphics} - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {Graphics} - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {Graphics} - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {Array} The path data used to construct the polygon. - * @return {Graphics} - */ -Graphics.prototype.drawPolygon = function (path) -{ - if (!(path instanceof Array)) - { - path = Array.prototype.slice.call(arguments); - } - - this.drawShape(new math.Polygon(path)); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {Graphics} - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty = true; - this.clearDirty = true; - this.graphicsData = []; - - return this; -}; - -/** - * Useful function that returns a texture of the graphics object that can then be used to create sprites - * This can be quite useful if your geometry is complicated and needs to be reused multiple times. - * - * @param resolution {number} The resolution of the texture being generated - * @param scaleMode {number} Should be one of the scaleMode consts - * @return {Texture} a texture of the graphics object - */ -Graphics.prototype.generateTexture = function (resolution, scaleMode) -{ - resolution = resolution || 1; - - var bounds = this.getBounds(); - - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); - - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - canvasBuffer.context.scale(resolution, resolution); - - canvasBuffer.context.translate(-bounds.x,-bounds.y); - - CanvasGraphics.renderGraphics(this, canvasBuffer.context); - - return texture; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (this.isMask === true) - { - return; - } - - // this code may still be needed so leaving for now.. - // - /* - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture on the gpu too! - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.worldAlpha = this.worldAlpha; - - Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); - - return; - } - - */ - - if (this.glDirty) - { - this.dirty = true; - this.glDirty = false; - } - - renderer.setObjectRenderer(renderer.objectRenderers.graphics); - renderer.objectRenderers.graphics.render(this); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} - * @private - */ -Graphics.prototype.renderCanvas = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.alpha <= 0 || this.isMask === true) - { - return; - } - - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.alpha = this.alpha; - - Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); - - return; - } - else - { - var context = renderer.context; - var transform = this.worldTransform; - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - var resolution = renderer.resolution; - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } - } -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @return {Rectangle} the rectangular bounding area - */ -Graphics.prototype.getBounds = function (matrix) -{ - // return an empty object if the item is a mask! - if (this.isMask) - { - return math.Rectangle.EMPTY; - } - - if (this.dirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.dirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - return this._bounds; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; - - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; -}; - -/** - * Generates the cached sprite when the sprite has cacheAsBitmap = true - * - * @private - */ -Graphics.prototype._generateCachedSprite = function () -{ - var bounds = this.getLocalBounds(); - - if (!this._cachedSprite) - { - var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); - var texture = Texture.fromCanvas(canvasBuffer.canvas); - - this._cachedSprite = new Sprite(texture); - this._cachedSprite.buffer = canvasBuffer; - - this._cachedSprite.worldTransform = this.worldTransform; - } - else - { - this._cachedSprite.buffer.resize(bounds.width, bounds.height); - } - - // leverage the anchor to account for the offset of the element - this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); - this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); - - // this._cachedSprite.buffer.context.save(); - this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); - - // make sure we set the alpha of the graphics to 1 for the render.. - this.worldAlpha = 1; - - // now render the graphic.. - CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); - - this._cachedSprite.alpha = this.alpha; -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Graphics.prototype.updateCachedSpriteTexture = function () -{ - var cachedSprite = this._cachedSprite; - var texture = cachedSprite.texture; - var canvas = cachedSprite.buffer.canvas; - - texture.baseTexture.width = canvas.width; - texture.baseTexture.height = canvas.height; - texture.crop.width = texture.frame.width = canvas.width; - texture.crop.height = texture.frame.height = canvas.height; - - cachedSprite._width = canvas.width; - cachedSprite._height = canvas.height; - - // update the dirty base textures - texture.baseTexture.dirty(); -}; - -/** - * Destroys a previous cached sprite. - * - */ -Graphics.prototype.destroyCachedSprite = function () -{ - this._cachedSprite.texture.destroy(true); - - // let the gc collect the unused sprite - // TODO could be object pooled! - this._cachedSprite = null; -}; - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. - * @return {GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = this.filling; - this.currentPath = data; - } - - this.dirty = true; - - return data; -}; diff --git a/src/core/primitives/GraphicsData.js b/src/core/primitives/GraphicsData.js deleted file mode 100644 index 788f6f2..0000000 --- a/src/core/primitives/GraphicsData.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * A GraphicsData object. - * - * @class - * @namespace PIXI - */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - this.lineWidth = lineWidth; - this.lineColor = lineColor; - this.lineAlpha = lineAlpha; - this._lineTint = lineColor; - - this.fillColor = fillColor; - this.fillAlpha = fillAlpha; - this._fillTint = fillColor; - this.fill = fill; - - this.shape = shape; - this.type = shape.type; -} - -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {GraphicsData} - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js deleted file mode 100644 index 1a508f9..0000000 --- a/src/core/primitives/GraphicsRenderer.js +++ /dev/null @@ -1,874 +0,0 @@ -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; - -/** - * 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 ); - -// var matrix = graphics.worldTransform.clone(); -// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); -// matrix.append(graphics.worldTransform); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.maskManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.maskManager.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.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); - - 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/primitives/WebGLGraphicsData.js b/src/core/primitives/WebGLGraphicsData.js deleted file mode 100755 index 41be102..0000000 --- a/src/core/primitives/WebGLGraphicsData.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @class - * @private - */ -function WebGLGraphicsData(gl) { - this.gl = gl; - - //TODO does this need to be split before uploding?? - this.color = [0,0,0]; // color split! - this.points = []; - this.indices = []; - this.buffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - this.mode = 1; - this.alpha = 1; - this.dirty = true; -} - -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; -module.exports = WebGLGraphicsData; - -/** - * - */ -WebGLGraphicsData.prototype.reset = function () { - this.points = []; - this.indices = []; -}; - -/** - * - */ -WebGLGraphicsData.prototype.upload = function () { - var gl = this.gl; - -// this.lastIndex = graphics.graphicsData.length; - this.glPoints = new Float32Array(this.points); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); - - this.glIndicies = new Uint16Array(this.indices); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); - - this.dirty = false; -}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6c1fb7d..4e82baa 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -174,6 +174,9 @@ // time to create the render managers! each one focuses on managing a state in webGL + // initialize the context so it is ready for the managers. + this._initContext(); + /** * Deals with managing the shader programs and their attribs * @member {WebGLShaderManager} @@ -204,17 +207,11 @@ */ this.blendModeManager = new WebGLBlendModeManager(this); - - this.blendModes = null; this._boundUpdateTexture = this.updateTexture.bind(this); this._boundDestroyTexture = this.destroyTexture.bind(this); - - - // time init the context.. - this._initContext(); this.currentRenderTarget = this.renderTarget; @@ -228,27 +225,18 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - /** - * Manages the renderer of specific objects. - * - * @member {object} - */ - this.objectRenderers = {}; - this.currentRenderer = new ObjectRenderer(); - - // create an instance of each registered object renderer - for (var o in WebGLRenderer._objectRenderers) { - this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); - } - + this.initPlugins(); } // constructor WebGLRenderer.prototype.constructor = WebGLRenderer; module.exports = WebGLRenderer; +WebGLRenderer.glContextId = 0; + +utils.pluginTarget.mixin(WebGLRenderer); utils.eventTarget.mixin(WebGLRenderer.prototype); Object.defineProperties(WebGLRenderer.prototype, { @@ -297,11 +285,9 @@ gl.enable(gl.BLEND); this.renderTarget = new RenderTarget(this.gl, this.width, this.height, null, true); - this.emit('context', gl); - // now resize and we are good to go! this.resize(this.width, this.height); }; @@ -349,7 +335,7 @@ gl.clear(gl.COLOR_BUFFER_BIT); } - this.renderDisplayObject(object, this.renderTarget)//this.projection); + this.renderDisplayObject(object, this.renderTarget);//this.projection); }; /** @@ -536,12 +522,7 @@ this.maskManager.destroy(); this.filterManager.destroy(); - for (var o in this.objectRenderers) { - this.objectRenderers[o].destroy(); - this.objectRenderers[o] = null; - } - - this.objectRenderers = null; + this.destroyPlugins(); // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -611,11 +592,3 @@ this.blendModes[CONST.blendModes.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; } }; - -WebGLRenderer.glContextId = 0; -WebGLRenderer._objectRenderers = {}; - -WebGLRenderer.registerObjectRenderer = function (name, ctor) { - WebGLRenderer._objectRenderers[name] = ctor; -}; - diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js index 87c99f0..d6ed3c6 100644 --- a/src/core/renderers/webgl/managers/WebGLFilterManager.js +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -51,7 +51,7 @@ */ WebGLFilterManager.prototype.begin = function (buffer) { - this.defaultShader = this.renderer.shaderManager.defaultShader; + this.defaultShader = this.renderer.shaderManager.plugins.defaultShader; this.width = this.renderer.projection.x * 2; this.height = -this.renderer.projection.y * 2; @@ -385,7 +385,7 @@ if (!shader) { - shader = new Shader(gl); + shader = new Shader(this.renderer.shaderManager); shader.fragmentSrc = filter.fragmentSrc; shader.uniforms = filter.uniforms; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js index 6a1f782..d6a11c1 100644 --- a/src/core/renderers/webgl/managers/WebGLMaskManager.js +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -126,11 +126,11 @@ // bind the graphics object.. var projection = this.renderer.projection, offset = this.renderer.offset, - shader;// = this.renderer.shaderManager.primitiveShader; + shader;// = this.renderer.shaderManager.plugins.primitiveShader; if (webGLData.mode === 1) { - shader = this.renderer.shaderManager.complexPrimitiveShader; + shader = this.renderer.shaderManager.plugins.complexPrimitiveShader; this.renderer.shaderManager.setShader(shader); @@ -156,7 +156,7 @@ else { //this.renderer.shaderManager.activatePrimitiveShader(); - shader = this.renderer.shaderManager.primitiveShader; + shader = this.renderer.shaderManager.plugins.primitiveShader; this.renderer.shaderManager.setShader( shader ); gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); @@ -292,11 +292,11 @@ */ WebGLMaskManager.prototype.pushMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); if (maskData.dirty) { - this.renderer.objectRenderers.graphics.updateGraphics(maskData, this.renderer.gl); + this.renderer.plugins.graphics.updateGraphics(maskData, this.renderer.gl); } if (!maskData._webGL[this.renderer.gl.id].data.length) @@ -314,7 +314,7 @@ */ WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); this.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); }; diff --git a/src/core/renderers/webgl/managers/WebGLShaderManager.js b/src/core/renderers/webgl/managers/WebGLShaderManager.js index 90c6477..9ebb58e 100644 --- a/src/core/renderers/webgl/managers/WebGLShaderManager.js +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -1,9 +1,5 @@ var WebGLManager = require('./WebGLManager'), - PrimitiveShader = require('../shaders/PrimitiveShader'), - ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), - Shader = require('../shaders/Shader'), - FastShader = require('../shaders/FastShader'), - StripShader = require('../shaders/StripShader'); + utils = require('../../../utils'); /** * @class @@ -51,41 +47,16 @@ */ this.currentShader = null; - // this shader is used for rendering primitives - this.primitiveShader = null; - - // this shader is used for rendering triangle strips - this.complexPrimitiveShader = null; - - // this shader is used for the default sprite rendering - this.defaultShader = null; - - // this shader is used for the fast sprite rendering - this.fastShader = null; - - // the next one is used for rendering triangle strips - this.stripShader = null; + this.initPlugins(); // listen for context and update necessary shaders var self = this; - this.renderer.on('context', function (event) + this.renderer.on('context', function () { - var gl = event.data; - - // this shader is used for rendering primitives - self.primitiveShader = new PrimitiveShader(gl); - - // this shader is used for rendering triangle strips - self.complexPrimitiveShader = new ComplexPrimitiveShader(gl); - - // this shader is used for the default sprite rendering - self.defaultShader = new Shader(gl); - - // this shader is used for the fast sprite rendering - self.fastShader = new FastShader(gl); - - // the next one is used for rendering triangle strips - self.stripShader = new StripShader(gl); + for (var o in this.plugins) + { + this.plugins[o] = new (this.plugins[o].constructor)(self); + } self.setShader(self.defaultShader); }); @@ -95,6 +66,8 @@ WebGLShaderManager.prototype.constructor = WebGLShaderManager; module.exports = WebGLShaderManager; +utils.pluginTarget.mixin(WebGLShaderManager); + /** * Takes the attributes given in parameters. * @@ -164,24 +137,11 @@ */ WebGLShaderManager.prototype.destroy = function () { + this.destroyPlugins(); + this.attribState = null; this.tempAttribState = null; - this.primitiveShader.destroy(); - this.primitiveShader = null; - - this.complexPrimitiveShader.destroy(); - this.complexPrimitiveShader = null; - - this.defaultShader.destroy(); - this.defaultShader = null; - - this.fastShader.destroy(); - this.fastShader = null; - - this.stripShader.destroy(); - this.stripShader = null; - this.renderer = null; }; diff --git a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js b/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js deleted file mode 100644 index f8a07da..0000000 --- a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js +++ /dev/null @@ -1,53 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function ComplexPrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform vec3 tint;', - 'uniform float alpha;', - 'uniform vec3 color;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - color: { type: '3f', value: [0,0,0] }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); -ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; -module.exports = ComplexPrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/FastShader.js b/src/core/renderers/webgl/shaders/FastShader.js index d198f10..c80d040 100644 --- a/src/core/renderers/webgl/shaders/FastShader.js +++ b/src/core/renderers/webgl/shaders/FastShader.js @@ -1,15 +1,16 @@ -var Shader = require('./Shader'); +var Shader = require('./Shader'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @extends Shader * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. */ -function FastShader(gl) +function FastShader(shaderManager) { Shader.call(this, - gl, + shaderManager, // vertex shader [ 'attribute vec2 aVertexPosition;', @@ -70,3 +71,5 @@ FastShader.prototype = Object.create(Shader.prototype); FastShader.prototype.constructor = FastShader; module.exports = FastShader; + +WebGLShaderManager.registerPlugin('fastShader', FastShader); diff --git a/src/core/renderers/webgl/shaders/PrimitiveShader.js b/src/core/renderers/webgl/shaders/PrimitiveShader.js deleted file mode 100644 index 753c475..0000000 --- a/src/core/renderers/webgl/shaders/PrimitiveShader.js +++ /dev/null @@ -1,55 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform float alpha;', - 'uniform float flipY;', - 'uniform vec3 tint;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aColor:0 - } - ); -} - -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; -module.exports = PrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/Shader.js b/src/core/renderers/webgl/shaders/Shader.js index d065a76..c60c5f9 100644 --- a/src/core/renderers/webgl/shaders/Shader.js +++ b/src/core/renderers/webgl/shaders/Shader.js @@ -1,12 +1,16 @@ -var utils = require('../../../utils'); +var utils = require('../../../utils'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @namespace PIXI - * @param [fragmentSrc] {string} The source of the fragment shader. + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. * @param [vertexSrc] {string} The source of the vertex shader. + * @param [fragmentSrc] {string} The source of the fragment shader. + * @param customUniforms {object} Custom uniforms to use to augment the built-in ones. + * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Shader(gl, vertexSrc, fragmentSrc, customUniforms, customAttributes) +function Shader(shaderManager, vertexSrc, fragmentSrc, customUniforms, customAttributes) { /** * @member {number} @@ -18,7 +22,7 @@ * @member {WebGLContext} * @readonly */ - this.gl = gl; + this.gl = shaderManager.renderer.gl; /** * The WebGL program. @@ -31,8 +35,8 @@ uSampler: { type: 'sampler2D', value: 0 }, projectionVector: { type: '2f', value: { x: 0, y: 0 } }, offsetVector: { type: '2f', value: { x: 0, y: 0 } }, - projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, - 0, 1, 0, + projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, + 0, 1, 0, 0, 0, 1) }, dimensions: { type: '4f', value: new Float32Array(4) } }; @@ -103,6 +107,8 @@ Shader.prototype.constructor = Shader; module.exports = Shader; +WebGLShaderManager.registerPlugin('defaultShader', Shader); + Shader.prototype.init = function () { this.compile(); @@ -506,4 +512,4 @@ } return shader; -}; \ No newline at end of file +}; diff --git a/src/core/renderers/webgl/shaders/SpriteShader.js b/src/core/renderers/webgl/shaders/SpriteShader.js deleted file mode 100644 index 1805b0a..0000000 --- a/src/core/renderers/webgl/shaders/SpriteShader.js +++ /dev/null @@ -1,42 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function SpriteShader(gl) -{ - Shader.call(this, - gl, - null, - // fragment shader - [ - 'precision lowp float;', - - 'varying vec2 vTextureCoord;', - 'varying vec4 vColor;', - - 'uniform sampler2D uSampler;', - - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aTextureCoord: 0, - aColor: 0 - } - ); -} - -SpriteShader.prototype = Object.create(Shader.prototype); -SpriteShader.prototype.constructor = SpriteShader; -module.exports = SpriteShader; diff --git a/src/core/renderers/webgl/shaders/StripShader.js b/src/core/renderers/webgl/shaders/StripShader.js deleted file mode 100644 index f487381..0000000 --- a/src/core/renderers/webgl/shaders/StripShader.js +++ /dev/null @@ -1,54 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function StripShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - // 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform vec2 projectionVector;', - 'uniform vec2 offsetVector;', - - 'varying vec2 vTextureCoord;', - - 'void main(void){', - ' vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);', - ' v -= offsetVector.xyx;', - ' gl_Position = vec4( v.x / projectionVector.x -1.0, v.y / -projectionVector.y + 1.0 , 0.0, 1.0);', - ' vTextureCoord = aTextureCoord;', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'uniform float alpha;', - 'uniform sampler2D uSampler;', - - 'varying vec2 vTextureCoord;', - - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y)) * alpha;', - '}' - ].join('\n'), - // custom uniforms - { - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -StripShader.prototype = Object.create(Shader.prototype); -StripShader.prototype.constructor = StripShader; -module.exports = StripShader; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index 0a34e55..43e2d00 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -33,7 +33,7 @@ * @property texture * @type Any */ - this.texture = null + this.texture = null; this.width = 0; this.height = 0; @@ -56,11 +56,11 @@ this.frameBuffer = gl.createFramebuffer(); - /* + /* A frame buffer needs a target to render to.. create a texture and bind it attach it to the framebuffer.. */ - + this.texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, this.texture); @@ -70,13 +70,13 @@ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); 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); - + gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer ); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0); - - /* + + /* The stencil buffer is used for masking in pixi lets create one and then add attach it to the framebuffer.. */ @@ -86,7 +86,7 @@ } this.resize(width, height); - + }; RenderTarget.prototype.constructor = RenderTarget; @@ -94,21 +94,21 @@ /** * Clears the filter texture. -* +* * @method clear */ RenderTarget.prototype.clear = function() { var gl = this.gl; - + gl.clearColor(0,0,0, 0); gl.clear(gl.COLOR_BUFFER_BIT); }; RenderTarget.prototype.activate = function() { - this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer ); -} + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer); +}; /** * Resizes the texture to the specified width and height @@ -119,25 +119,25 @@ */ RenderTarget.prototype.resize = function(width, height) { - width = width | 0; - height = height | 0; - - if(this.width === width && this.height === height) return; + if (this.width === width && this.height === height) { + return; + } this.width = width; this.height = height; + this.projectionMatrix = new math.Matrix(); - - if(!this.root) + + if (!this.root) { var gl = this.gl; this.projectionMatrix.a = 1/width*2; - this.projectionMatrix.d = 1/height*2; + this.projectionMatrix.d = -1/height*2; this.projectionMatrix.tx = -1; - this.projectionMatrix.ty = -1; + this.projectionMatrix.ty = 1; gl.bindTexture(gl.TEXTURE_2D, this.texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width , height , 0, gl.RGBA, gl.UNSIGNED_BYTE, null); @@ -157,7 +157,7 @@ /** * Destroys the filter texture. -* +* * @method destroy */ RenderTarget.prototype.destroy = function() diff --git a/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js b/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js index a63f4d7..3f37b7d 100644 --- a/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js +++ b/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js @@ -183,7 +183,7 @@ */ WebGLFastSpriteBatch.prototype.begin = function (spriteBatch) { - this.shader = this.renderer.shaderManager.fastShader; + this.shader = this.renderer.shaderManager.plugins.fastShader; this.matrix = spriteBatch.worldTransform.toArray(true); diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index bf8db28..2dc47ff 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -201,8 +201,8 @@ Sprite.prototype._renderWebGL = function (renderer) { - renderer.setObjectRenderer(renderer.objectRenderers.sprite); - renderer.objectRenderers.sprite.render(this); + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); }; /** diff --git a/src/core/sprites/SpriteBatch.js b/src/core/sprites/SpriteBatch.js index 63cebc0..4fe33e4 100644 --- a/src/core/sprites/SpriteBatch.js +++ b/src/core/sprites/SpriteBatch.js @@ -61,7 +61,7 @@ renderer.spriteBatch.stop(); - renderer.shaderManager.setShader(renderer.shaderManager.fastShader); + renderer.shaderManager.setShader(renderer.shaderManager.plugins.fastShader); renderer.fastSpriteBatch.begin(this); renderer.fastSpriteBatch.render(this); diff --git a/src/core/sprites/SpriteRenderer.js b/src/core/sprites/SpriteRenderer.js deleted file mode 100644 index fd9a802..0000000 --- a/src/core/sprites/SpriteRenderer.js +++ /dev/null @@ -1,537 +0,0 @@ -var ObjectRenderer = require('../renderers/webgl/utils/ObjectRenderer'), - Shader = require('../renderers/webgl/shaders/Shader'), - SpriteShader = require('../renderers/webgl/shaders/SpriteShader'), - WebGLRenderer = require('../renderers/webgl/WebGLRenderer'); - math = require('../math'); - -/** - * @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.setupContext(); -} - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; -module.exports = 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 = sprite; - 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 - gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, this.renderer.currentRenderTarget.projectionMatrix.toArray(true)); - } - } - - 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; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js new file mode 100644 index 0000000..c006ba4 --- /dev/null +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -0,0 +1,533 @@ +var ObjectRenderer = require('../../renderers/webgl/utils/ObjectRenderer'), + Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLRenderer = require('../../renderers/webgl/WebGLRenderer'), + SpriteShader = require('./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) +{ + 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; + + this.setupContext(); +} + +SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); +SpriteRenderer.prototype.constructor = SpriteRenderer; +module.exports = SpriteRenderer; + +WebGLRenderer.registerPlugin('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(this.renderer.shaderManager); + + // 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(this.renderer.shaderManager, 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 + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, this.renderer.currentRenderTarget.projectionMatrix.toArray(true)); + } + } + + 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; +}; diff --git a/src/core/sprites/webgl/SpriteShader.js b/src/core/sprites/webgl/SpriteShader.js new file mode 100644 index 0000000..3925e9f --- /dev/null +++ b/src/core/sprites/webgl/SpriteShader.js @@ -0,0 +1,45 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function SpriteShader(shaderManager) +{ + Shader.call(this, + shaderManager, + null, + // fragment shader + [ + 'precision lowp float;', + + 'varying vec2 vTextureCoord;', + 'varying vec4 vColor;', + + 'uniform sampler2D uSampler;', + + 'void main(void){', + ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aTextureCoord: 0, + aColor: 0 + } + ); +} + +SpriteShader.prototype = Object.create(Shader.prototype); +SpriteShader.prototype.constructor = SpriteShader; +module.exports = SpriteShader; + +WebGLShaderManager.registerPlugin('fastShader', SpriteShader); diff --git a/src/core/utils/index.js b/src/core/utils/index.js index bfae7ae..1d451c0 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -7,9 +7,10 @@ _uid: 0, _saidHello: false, - PolyK: require('./PolyK'), - EventData: require('./EventData'), - eventTarget: require('./eventTarget'), + PolyK: require('./PolyK'), + EventData: require('./EventData'), + eventTarget: require('./eventTarget'), + pluginTarget: require('./pluginTarget'), /** * Gets the next uuid diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js new file mode 100644 index 0000000..41be102 --- /dev/null +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -0,0 +1,48 @@ +/** + * @class + * @private + */ +function WebGLGraphicsData(gl) { + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +} + +WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; +module.exports = WebGLGraphicsData; + +/** + * + */ +WebGLGraphicsData.prototype.reset = function () { + this.points = []; + this.indices = []; +}; + +/** + * + */ +WebGLGraphicsData.prototype.upload = function () { + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; diff --git a/src/core/index.js b/src/core/index.js index 6f744e9..1d0a398 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -23,12 +23,12 @@ Sprite: require('./sprites/Sprite'), SpriteBatch: require('./sprites/SpriteBatch'), - SpriteRenderer: require('./sprites/SpriteRenderer'), + SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), // primitives - Graphics: require('./primitives/Graphics'), - GraphicsData: require('./primitives/GraphicsData'), - GraphicsRenderer: require('./primitives/GraphicsRenderer'), + Graphics: require('./graphics/Graphics'), + GraphicsData: require('./graphics/GraphicsData'), + GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,10 +42,8 @@ CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), // renderers - webgl - WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), - // WebGLGraphics: require('./renderers/webgl/utils/WebGLGraphics'), - - + WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), + Shader: require('./renderers/webgl/shaders/Shader'), /** * This helper function will automatically detect which renderer you should be using. @@ -103,7 +101,3 @@ return core.autoDetectRenderer(width, height, options, isAndroid); } }; - -// Adding here for now.. sure there is a better place! -core.WebGLRenderer.registerObjectRenderer('sprite', core.SpriteRenderer); -core.WebGLRenderer.registerObjectRenderer('graphics', core.GraphicsRenderer); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index a086950..81de05c 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -268,6 +268,6 @@ matrix.ty = this.ty; return matrix; -} +}; Matrix.IDENTITY = new Matrix(); diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js deleted file mode 100644 index 8db9b45..0000000 --- a/src/core/primitives/Graphics.js +++ /dev/null @@ -1,1116 +0,0 @@ -var DisplayObjectContainer = require('../display/DisplayObjectContainer'), - //Sprite = require('../display/Sprite'), - Texture = require('../textures/Texture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), - // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), - GraphicsData = require('./GraphicsData'), - math = require('../math'), - CONST = require('../const'); - -/** - * The Graphics class contains methods used to draw primitive shapes such as lines, circles and - * rectangles to the display, and color and fill them. - * - * @class - * @extends DisplayObjectContainer - * @namespace PIXI - */ -function Graphics() -{ - DisplayObjectContainer.call(this); - - this.renderable = true; - - /** - * The alpha value used when filling the Graphics object. - * - * @member {number} - * @default 1 - */ - this.fillAlpha = 1; - - /** - * The width (thickness) of any lines drawn. - * - * @member {number} - * @default 0 - */ - this.lineWidth = 0; - - /** - * The color of any lines drawn. - * - * @member {string} - * @default 0 - */ - this.lineColor = 0; - - /** - * Graphics data - * - * @member {GraphicsData[]} - * @private - */ - this.graphicsData = []; - - /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * Current path - * - * @member {GraphicsData} - * @private - */ - this.currentPath = null; - - /** - * Array containing some WebGL-related properties used by the WebGL renderer. - * - * @member {object} - * @private - */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; - - /** - * Whether this shape is being used as a mask. - * - * @member {boolean} - */ - this.isMask = false; - - /** - * The bounds' padding used for bounds calculation. - * - * @member {number} - */ - this.boundsPadding = 0; - - /** - * A cache of the local bounds to prevent recalculation. - * - * @member {Rectangle} - * @private - */ - this._localBounds = new math.Rectangle(0,0,1,1); - - /** - * Used to detect if the graphics object has changed. If this is set to true then the graphics - * object will be recalculated. - * - * @member {boolean} - * @private - */ - this.dirty = true; - - /** - * Used to detect if the WebGL graphics object has changed. If this is set to true then the - * graphics object will be recalculated. - * - * @member {boolean} - * @private - */ - this.glDirty = false; - - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; -} - -// constructor -Graphics.prototype = Object.create(DisplayObjectContainer.prototype); -Graphics.prototype.constructor = Graphics; -module.exports = Graphics; - -Object.defineProperties(Graphics.prototype, { - /** - * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. - * This is useful if your graphics element does not change often, as it will speed up the rendering - * of the object in exchange for taking up texture memory. It is also useful if you need the graphics - * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if - * you are constantly redrawing the graphics element. - * - * @member {boolean} - * @memberof Graphics# - * @default false - * @private - */ - cacheAsBitmap: { - get: function () - { - return this._cacheAsBitmap; - }, - set: function (value) - { - this._cacheAsBitmap = value; - - if (this._cacheAsBitmap) - { - this._generateCachedSprite(); - } - else - { - this.destroyCachedSprite(); - this.dirty = true; - } - } - } -}); - -/** - * Creates a new Graphics object with the same values as this one. - * - * @return {Graphics} - */ -GraphicsData.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = this.dirty; - clone.glDirty = this.glDirty; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData.clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {Graphics} - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (arguments.length < 3) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); - } - else - { - // otherwise its empty so lets just set the line properties - this.currentPath.lineWidth = this.lineWidth; - this.currentPath.lineColor = this.lineColor; - this.currentPath.lineAlpha = this.lineAlpha; - } - } - - return this; -}; - -/** - * Moves the current drawing position to x, y. - * - * @param x {number} the X coordinate to move to - * @param y {number} the Y coordinate to move to - * @return {Graphics} - */ -Graphics.prototype.moveTo = function (x, y) -{ - this.drawShape(new math.Polygon([x,y])); - - return this; -}; - -/** - * Draws a line using the current line style from the current drawing position to (x, y); - * The current drawing position is then set to (x, y). - * - * @param x {number} the X coordinate to draw to - * @param y {number} the Y coordinate to draw to - * @return {Graphics} - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a quadratic bezier curve and then draws it. - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @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 {Graphics} - */ -Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var xa, - ya, - n = 20, - points = this.currentPath.shape.points; - - if (points.length === 0) - { - this.moveTo(0, 0); - } - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - for (var i = 1; i <= n; ++i) - { - j = i / n; - - xa = fromX + ( (cpX - fromX) * j ); - ya = fromY + ( (cpY - fromY) * j ); - - points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), - ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); - } - - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a bezier curve and then draws it. - * - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param cpX2 {number} Second Control point x - * @param cpY2 {number} Second Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {Graphics} - */ -Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var n = 20, - dt, - dt2, - dt3, - t2, - t3, - points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - - for (var i = 1; i <= n; ++i) - { - j = i / n; - - dt = (1 - j); - dt2 = dt * dt; - dt3 = dt2 * dt; - - t2 = j * j; - t3 = t2 * j; - - points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, - dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); - } - - this.dirty = true; - - return this; -}; - -/** - * The arcTo() method creates an arc/curve between two tangents on the canvas. - * - * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! - * - * @param x1 {number} The x-coordinate of the beginning of the arc - * @param y1 {number} The y-coordinate of the beginning of the arc - * @param x2 {number} The x-coordinate of the end of the arc - * @param y2 {number} The y-coordinate of the end of the arc - * @param radius {number} The radius of the arc - * @return {Graphics} - */ -Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points.push(x1, y1); - } - } - else - { - this.moveTo(x1, y1); - } - - var points = this.currentPath.shape.points, - fromX = points[points.length-2], - fromY = points[points.length-1], - a1 = fromY - y1, - b1 = fromX - x1, - a2 = y2 - y1, - b2 = x2 - x1, - mm = Math.abs(a1 * b2 - b1 * a2); - - if (mm < 1.0e-8 || radius === 0) - { - if (points[points.length-2] !== x1 || points[points.length-1] !== y1) - { - points.push(x1, y1); - } - } - else - { - var dd = a1 * a1 + b1 * b1, - cc = a2 * a2 + b2 * b2, - tt = a1 * a2 + b1 * b2, - k1 = radius * Math.sqrt(dd) / mm, - k2 = radius * Math.sqrt(cc) / mm, - j1 = k1 * tt / dd, - j2 = k2 * tt / cc, - cx = k1 * b2 + k2 * b1, - cy = k1 * a2 + k2 * a1, - px = b1 * (k2 + j1), - py = a1 * (k2 + j1), - qx = b2 * (k1 + j2), - qy = a2 * (k1 + j2), - startAngle = Math.atan2(py - cy, px - cx), - endAngle = Math.atan2(qy - cy, qx - cx); - - this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); - } - - this.dirty = true; - - return this; -}; - -/** - * The arc method creates an arc/curve (used to create circles, or parts of circles). - * - * @param cx {number} The x-coordinate of the center of the circle - * @param cy {number} The y-coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) - * @param endAngle {number} The ending angle, in radians - * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {Graphics} - */ -Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - var points; - - // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... - if (this.currentPath) - { - points = this.currentPath.shape.points; - - if (points.length === 0) - { - points.push(startX, startY); - } - else if (points[points.length-2] !== startX || points[points.length-1] !== startY) - { - points.push(startX, startY); - } - } - else - { - this.moveTo(startX, startY); - points = this.currentPath.shape.points; - } - - if (startAngle === endAngle) - { - return this; - } - - if (!anticlockwise && endAngle <= startAngle) - { - endAngle += Math.PI * 2; - } - else if (anticlockwise && startAngle <= endAngle) - { - startAngle += Math.PI * 2; - } - - var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); - var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; - - if (sweep === 0) - { - return this; - } - - var theta = sweep/(segs*2); - var theta2 = theta*2; - - var cTheta = Math.cos(theta); - var sTheta = Math.sin(theta); - - var segMinus = segs - 1; - - var remainder = ( segMinus % 1 ) / segMinus; - - for (var i = 0; i <= segMinus; ++i) - { - var real = i + remainder * i; - var angle = ((theta) + startAngle + (theta2 * real)); - - var c = Math.cos(angle); - var s = -Math.sin(angle); - - points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, - ( (cTheta * -s) + (sTheta * c) ) * radius + cy); - } - - this.dirty = true; - - return this; -}; - -/** - * Specifies a simple one-color fill that subsequent calls to other Graphics methods - * (such as lineTo() or drawCircle()) use when drawing. - * - * @param color {number} the color of the fill - * @param alpha {number} the alpha of the fill - * @return {Graphics} - */ -Graphics.prototype.beginFill = function (color, alpha) -{ - this.filling = true; - this.fillColor = color || 0; - this.fillAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length <= 2) - { - this.currentPath.fill = this.filling; - this.currentPath.fillColor = this.fillColor; - this.currentPath.fillAlpha = this.fillAlpha; - } - } - return this; -}; - -/** - * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. - * - * @return {Graphics} - */ -Graphics.prototype.endFill = function () -{ - this.filling = false; - this.fillColor = null; - this.fillAlpha = 1; - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @return {Graphics} - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {Graphics} - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {Graphics} - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {Array} The path data used to construct the polygon. - * @return {Graphics} - */ -Graphics.prototype.drawPolygon = function (path) -{ - if (!(path instanceof Array)) - { - path = Array.prototype.slice.call(arguments); - } - - this.drawShape(new math.Polygon(path)); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {Graphics} - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty = true; - this.clearDirty = true; - this.graphicsData = []; - - return this; -}; - -/** - * Useful function that returns a texture of the graphics object that can then be used to create sprites - * This can be quite useful if your geometry is complicated and needs to be reused multiple times. - * - * @param resolution {number} The resolution of the texture being generated - * @param scaleMode {number} Should be one of the scaleMode consts - * @return {Texture} a texture of the graphics object - */ -Graphics.prototype.generateTexture = function (resolution, scaleMode) -{ - resolution = resolution || 1; - - var bounds = this.getBounds(); - - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); - - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - canvasBuffer.context.scale(resolution, resolution); - - canvasBuffer.context.translate(-bounds.x,-bounds.y); - - CanvasGraphics.renderGraphics(this, canvasBuffer.context); - - return texture; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (this.isMask === true) - { - return; - } - - // this code may still be needed so leaving for now.. - // - /* - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture on the gpu too! - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.worldAlpha = this.worldAlpha; - - Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); - - return; - } - - */ - - if (this.glDirty) - { - this.dirty = true; - this.glDirty = false; - } - - renderer.setObjectRenderer(renderer.objectRenderers.graphics); - renderer.objectRenderers.graphics.render(this); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} - * @private - */ -Graphics.prototype.renderCanvas = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.alpha <= 0 || this.isMask === true) - { - return; - } - - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.alpha = this.alpha; - - Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); - - return; - } - else - { - var context = renderer.context; - var transform = this.worldTransform; - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - var resolution = renderer.resolution; - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } - } -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @return {Rectangle} the rectangular bounding area - */ -Graphics.prototype.getBounds = function (matrix) -{ - // return an empty object if the item is a mask! - if (this.isMask) - { - return math.Rectangle.EMPTY; - } - - if (this.dirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.dirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - return this._bounds; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; - - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; -}; - -/** - * Generates the cached sprite when the sprite has cacheAsBitmap = true - * - * @private - */ -Graphics.prototype._generateCachedSprite = function () -{ - var bounds = this.getLocalBounds(); - - if (!this._cachedSprite) - { - var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); - var texture = Texture.fromCanvas(canvasBuffer.canvas); - - this._cachedSprite = new Sprite(texture); - this._cachedSprite.buffer = canvasBuffer; - - this._cachedSprite.worldTransform = this.worldTransform; - } - else - { - this._cachedSprite.buffer.resize(bounds.width, bounds.height); - } - - // leverage the anchor to account for the offset of the element - this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); - this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); - - // this._cachedSprite.buffer.context.save(); - this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); - - // make sure we set the alpha of the graphics to 1 for the render.. - this.worldAlpha = 1; - - // now render the graphic.. - CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); - - this._cachedSprite.alpha = this.alpha; -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Graphics.prototype.updateCachedSpriteTexture = function () -{ - var cachedSprite = this._cachedSprite; - var texture = cachedSprite.texture; - var canvas = cachedSprite.buffer.canvas; - - texture.baseTexture.width = canvas.width; - texture.baseTexture.height = canvas.height; - texture.crop.width = texture.frame.width = canvas.width; - texture.crop.height = texture.frame.height = canvas.height; - - cachedSprite._width = canvas.width; - cachedSprite._height = canvas.height; - - // update the dirty base textures - texture.baseTexture.dirty(); -}; - -/** - * Destroys a previous cached sprite. - * - */ -Graphics.prototype.destroyCachedSprite = function () -{ - this._cachedSprite.texture.destroy(true); - - // let the gc collect the unused sprite - // TODO could be object pooled! - this._cachedSprite = null; -}; - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. - * @return {GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = this.filling; - this.currentPath = data; - } - - this.dirty = true; - - return data; -}; diff --git a/src/core/primitives/GraphicsData.js b/src/core/primitives/GraphicsData.js deleted file mode 100644 index 788f6f2..0000000 --- a/src/core/primitives/GraphicsData.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * A GraphicsData object. - * - * @class - * @namespace PIXI - */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - this.lineWidth = lineWidth; - this.lineColor = lineColor; - this.lineAlpha = lineAlpha; - this._lineTint = lineColor; - - this.fillColor = fillColor; - this.fillAlpha = fillAlpha; - this._fillTint = fillColor; - this.fill = fill; - - this.shape = shape; - this.type = shape.type; -} - -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {GraphicsData} - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js deleted file mode 100644 index 1a508f9..0000000 --- a/src/core/primitives/GraphicsRenderer.js +++ /dev/null @@ -1,874 +0,0 @@ -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; - -/** - * 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 ); - -// var matrix = graphics.worldTransform.clone(); -// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); -// matrix.append(graphics.worldTransform); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.maskManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.maskManager.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.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); - - 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/primitives/WebGLGraphicsData.js b/src/core/primitives/WebGLGraphicsData.js deleted file mode 100755 index 41be102..0000000 --- a/src/core/primitives/WebGLGraphicsData.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @class - * @private - */ -function WebGLGraphicsData(gl) { - this.gl = gl; - - //TODO does this need to be split before uploding?? - this.color = [0,0,0]; // color split! - this.points = []; - this.indices = []; - this.buffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - this.mode = 1; - this.alpha = 1; - this.dirty = true; -} - -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; -module.exports = WebGLGraphicsData; - -/** - * - */ -WebGLGraphicsData.prototype.reset = function () { - this.points = []; - this.indices = []; -}; - -/** - * - */ -WebGLGraphicsData.prototype.upload = function () { - var gl = this.gl; - -// this.lastIndex = graphics.graphicsData.length; - this.glPoints = new Float32Array(this.points); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); - - this.glIndicies = new Uint16Array(this.indices); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); - - this.dirty = false; -}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6c1fb7d..4e82baa 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -174,6 +174,9 @@ // time to create the render managers! each one focuses on managing a state in webGL + // initialize the context so it is ready for the managers. + this._initContext(); + /** * Deals with managing the shader programs and their attribs * @member {WebGLShaderManager} @@ -204,17 +207,11 @@ */ this.blendModeManager = new WebGLBlendModeManager(this); - - this.blendModes = null; this._boundUpdateTexture = this.updateTexture.bind(this); this._boundDestroyTexture = this.destroyTexture.bind(this); - - - // time init the context.. - this._initContext(); this.currentRenderTarget = this.renderTarget; @@ -228,27 +225,18 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - /** - * Manages the renderer of specific objects. - * - * @member {object} - */ - this.objectRenderers = {}; - this.currentRenderer = new ObjectRenderer(); - - // create an instance of each registered object renderer - for (var o in WebGLRenderer._objectRenderers) { - this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); - } - + this.initPlugins(); } // constructor WebGLRenderer.prototype.constructor = WebGLRenderer; module.exports = WebGLRenderer; +WebGLRenderer.glContextId = 0; + +utils.pluginTarget.mixin(WebGLRenderer); utils.eventTarget.mixin(WebGLRenderer.prototype); Object.defineProperties(WebGLRenderer.prototype, { @@ -297,11 +285,9 @@ gl.enable(gl.BLEND); this.renderTarget = new RenderTarget(this.gl, this.width, this.height, null, true); - this.emit('context', gl); - // now resize and we are good to go! this.resize(this.width, this.height); }; @@ -349,7 +335,7 @@ gl.clear(gl.COLOR_BUFFER_BIT); } - this.renderDisplayObject(object, this.renderTarget)//this.projection); + this.renderDisplayObject(object, this.renderTarget);//this.projection); }; /** @@ -536,12 +522,7 @@ this.maskManager.destroy(); this.filterManager.destroy(); - for (var o in this.objectRenderers) { - this.objectRenderers[o].destroy(); - this.objectRenderers[o] = null; - } - - this.objectRenderers = null; + this.destroyPlugins(); // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -611,11 +592,3 @@ this.blendModes[CONST.blendModes.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; } }; - -WebGLRenderer.glContextId = 0; -WebGLRenderer._objectRenderers = {}; - -WebGLRenderer.registerObjectRenderer = function (name, ctor) { - WebGLRenderer._objectRenderers[name] = ctor; -}; - diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js index 87c99f0..d6ed3c6 100644 --- a/src/core/renderers/webgl/managers/WebGLFilterManager.js +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -51,7 +51,7 @@ */ WebGLFilterManager.prototype.begin = function (buffer) { - this.defaultShader = this.renderer.shaderManager.defaultShader; + this.defaultShader = this.renderer.shaderManager.plugins.defaultShader; this.width = this.renderer.projection.x * 2; this.height = -this.renderer.projection.y * 2; @@ -385,7 +385,7 @@ if (!shader) { - shader = new Shader(gl); + shader = new Shader(this.renderer.shaderManager); shader.fragmentSrc = filter.fragmentSrc; shader.uniforms = filter.uniforms; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js index 6a1f782..d6a11c1 100644 --- a/src/core/renderers/webgl/managers/WebGLMaskManager.js +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -126,11 +126,11 @@ // bind the graphics object.. var projection = this.renderer.projection, offset = this.renderer.offset, - shader;// = this.renderer.shaderManager.primitiveShader; + shader;// = this.renderer.shaderManager.plugins.primitiveShader; if (webGLData.mode === 1) { - shader = this.renderer.shaderManager.complexPrimitiveShader; + shader = this.renderer.shaderManager.plugins.complexPrimitiveShader; this.renderer.shaderManager.setShader(shader); @@ -156,7 +156,7 @@ else { //this.renderer.shaderManager.activatePrimitiveShader(); - shader = this.renderer.shaderManager.primitiveShader; + shader = this.renderer.shaderManager.plugins.primitiveShader; this.renderer.shaderManager.setShader( shader ); gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); @@ -292,11 +292,11 @@ */ WebGLMaskManager.prototype.pushMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); if (maskData.dirty) { - this.renderer.objectRenderers.graphics.updateGraphics(maskData, this.renderer.gl); + this.renderer.plugins.graphics.updateGraphics(maskData, this.renderer.gl); } if (!maskData._webGL[this.renderer.gl.id].data.length) @@ -314,7 +314,7 @@ */ WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); this.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); }; diff --git a/src/core/renderers/webgl/managers/WebGLShaderManager.js b/src/core/renderers/webgl/managers/WebGLShaderManager.js index 90c6477..9ebb58e 100644 --- a/src/core/renderers/webgl/managers/WebGLShaderManager.js +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -1,9 +1,5 @@ var WebGLManager = require('./WebGLManager'), - PrimitiveShader = require('../shaders/PrimitiveShader'), - ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), - Shader = require('../shaders/Shader'), - FastShader = require('../shaders/FastShader'), - StripShader = require('../shaders/StripShader'); + utils = require('../../../utils'); /** * @class @@ -51,41 +47,16 @@ */ this.currentShader = null; - // this shader is used for rendering primitives - this.primitiveShader = null; - - // this shader is used for rendering triangle strips - this.complexPrimitiveShader = null; - - // this shader is used for the default sprite rendering - this.defaultShader = null; - - // this shader is used for the fast sprite rendering - this.fastShader = null; - - // the next one is used for rendering triangle strips - this.stripShader = null; + this.initPlugins(); // listen for context and update necessary shaders var self = this; - this.renderer.on('context', function (event) + this.renderer.on('context', function () { - var gl = event.data; - - // this shader is used for rendering primitives - self.primitiveShader = new PrimitiveShader(gl); - - // this shader is used for rendering triangle strips - self.complexPrimitiveShader = new ComplexPrimitiveShader(gl); - - // this shader is used for the default sprite rendering - self.defaultShader = new Shader(gl); - - // this shader is used for the fast sprite rendering - self.fastShader = new FastShader(gl); - - // the next one is used for rendering triangle strips - self.stripShader = new StripShader(gl); + for (var o in this.plugins) + { + this.plugins[o] = new (this.plugins[o].constructor)(self); + } self.setShader(self.defaultShader); }); @@ -95,6 +66,8 @@ WebGLShaderManager.prototype.constructor = WebGLShaderManager; module.exports = WebGLShaderManager; +utils.pluginTarget.mixin(WebGLShaderManager); + /** * Takes the attributes given in parameters. * @@ -164,24 +137,11 @@ */ WebGLShaderManager.prototype.destroy = function () { + this.destroyPlugins(); + this.attribState = null; this.tempAttribState = null; - this.primitiveShader.destroy(); - this.primitiveShader = null; - - this.complexPrimitiveShader.destroy(); - this.complexPrimitiveShader = null; - - this.defaultShader.destroy(); - this.defaultShader = null; - - this.fastShader.destroy(); - this.fastShader = null; - - this.stripShader.destroy(); - this.stripShader = null; - this.renderer = null; }; diff --git a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js b/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js deleted file mode 100644 index f8a07da..0000000 --- a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js +++ /dev/null @@ -1,53 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function ComplexPrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform vec3 tint;', - 'uniform float alpha;', - 'uniform vec3 color;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - color: { type: '3f', value: [0,0,0] }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); -ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; -module.exports = ComplexPrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/FastShader.js b/src/core/renderers/webgl/shaders/FastShader.js index d198f10..c80d040 100644 --- a/src/core/renderers/webgl/shaders/FastShader.js +++ b/src/core/renderers/webgl/shaders/FastShader.js @@ -1,15 +1,16 @@ -var Shader = require('./Shader'); +var Shader = require('./Shader'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @extends Shader * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. */ -function FastShader(gl) +function FastShader(shaderManager) { Shader.call(this, - gl, + shaderManager, // vertex shader [ 'attribute vec2 aVertexPosition;', @@ -70,3 +71,5 @@ FastShader.prototype = Object.create(Shader.prototype); FastShader.prototype.constructor = FastShader; module.exports = FastShader; + +WebGLShaderManager.registerPlugin('fastShader', FastShader); diff --git a/src/core/renderers/webgl/shaders/PrimitiveShader.js b/src/core/renderers/webgl/shaders/PrimitiveShader.js deleted file mode 100644 index 753c475..0000000 --- a/src/core/renderers/webgl/shaders/PrimitiveShader.js +++ /dev/null @@ -1,55 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform float alpha;', - 'uniform float flipY;', - 'uniform vec3 tint;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aColor:0 - } - ); -} - -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; -module.exports = PrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/Shader.js b/src/core/renderers/webgl/shaders/Shader.js index d065a76..c60c5f9 100644 --- a/src/core/renderers/webgl/shaders/Shader.js +++ b/src/core/renderers/webgl/shaders/Shader.js @@ -1,12 +1,16 @@ -var utils = require('../../../utils'); +var utils = require('../../../utils'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @namespace PIXI - * @param [fragmentSrc] {string} The source of the fragment shader. + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. * @param [vertexSrc] {string} The source of the vertex shader. + * @param [fragmentSrc] {string} The source of the fragment shader. + * @param customUniforms {object} Custom uniforms to use to augment the built-in ones. + * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Shader(gl, vertexSrc, fragmentSrc, customUniforms, customAttributes) +function Shader(shaderManager, vertexSrc, fragmentSrc, customUniforms, customAttributes) { /** * @member {number} @@ -18,7 +22,7 @@ * @member {WebGLContext} * @readonly */ - this.gl = gl; + this.gl = shaderManager.renderer.gl; /** * The WebGL program. @@ -31,8 +35,8 @@ uSampler: { type: 'sampler2D', value: 0 }, projectionVector: { type: '2f', value: { x: 0, y: 0 } }, offsetVector: { type: '2f', value: { x: 0, y: 0 } }, - projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, - 0, 1, 0, + projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, + 0, 1, 0, 0, 0, 1) }, dimensions: { type: '4f', value: new Float32Array(4) } }; @@ -103,6 +107,8 @@ Shader.prototype.constructor = Shader; module.exports = Shader; +WebGLShaderManager.registerPlugin('defaultShader', Shader); + Shader.prototype.init = function () { this.compile(); @@ -506,4 +512,4 @@ } return shader; -}; \ No newline at end of file +}; diff --git a/src/core/renderers/webgl/shaders/SpriteShader.js b/src/core/renderers/webgl/shaders/SpriteShader.js deleted file mode 100644 index 1805b0a..0000000 --- a/src/core/renderers/webgl/shaders/SpriteShader.js +++ /dev/null @@ -1,42 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function SpriteShader(gl) -{ - Shader.call(this, - gl, - null, - // fragment shader - [ - 'precision lowp float;', - - 'varying vec2 vTextureCoord;', - 'varying vec4 vColor;', - - 'uniform sampler2D uSampler;', - - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aTextureCoord: 0, - aColor: 0 - } - ); -} - -SpriteShader.prototype = Object.create(Shader.prototype); -SpriteShader.prototype.constructor = SpriteShader; -module.exports = SpriteShader; diff --git a/src/core/renderers/webgl/shaders/StripShader.js b/src/core/renderers/webgl/shaders/StripShader.js deleted file mode 100644 index f487381..0000000 --- a/src/core/renderers/webgl/shaders/StripShader.js +++ /dev/null @@ -1,54 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function StripShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - // 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform vec2 projectionVector;', - 'uniform vec2 offsetVector;', - - 'varying vec2 vTextureCoord;', - - 'void main(void){', - ' vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);', - ' v -= offsetVector.xyx;', - ' gl_Position = vec4( v.x / projectionVector.x -1.0, v.y / -projectionVector.y + 1.0 , 0.0, 1.0);', - ' vTextureCoord = aTextureCoord;', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'uniform float alpha;', - 'uniform sampler2D uSampler;', - - 'varying vec2 vTextureCoord;', - - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y)) * alpha;', - '}' - ].join('\n'), - // custom uniforms - { - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -StripShader.prototype = Object.create(Shader.prototype); -StripShader.prototype.constructor = StripShader; -module.exports = StripShader; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index 0a34e55..43e2d00 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -33,7 +33,7 @@ * @property texture * @type Any */ - this.texture = null + this.texture = null; this.width = 0; this.height = 0; @@ -56,11 +56,11 @@ this.frameBuffer = gl.createFramebuffer(); - /* + /* A frame buffer needs a target to render to.. create a texture and bind it attach it to the framebuffer.. */ - + this.texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, this.texture); @@ -70,13 +70,13 @@ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); 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); - + gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer ); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0); - - /* + + /* The stencil buffer is used for masking in pixi lets create one and then add attach it to the framebuffer.. */ @@ -86,7 +86,7 @@ } this.resize(width, height); - + }; RenderTarget.prototype.constructor = RenderTarget; @@ -94,21 +94,21 @@ /** * Clears the filter texture. -* +* * @method clear */ RenderTarget.prototype.clear = function() { var gl = this.gl; - + gl.clearColor(0,0,0, 0); gl.clear(gl.COLOR_BUFFER_BIT); }; RenderTarget.prototype.activate = function() { - this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer ); -} + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer); +}; /** * Resizes the texture to the specified width and height @@ -119,25 +119,25 @@ */ RenderTarget.prototype.resize = function(width, height) { - width = width | 0; - height = height | 0; - - if(this.width === width && this.height === height) return; + if (this.width === width && this.height === height) { + return; + } this.width = width; this.height = height; + this.projectionMatrix = new math.Matrix(); - - if(!this.root) + + if (!this.root) { var gl = this.gl; this.projectionMatrix.a = 1/width*2; - this.projectionMatrix.d = 1/height*2; + this.projectionMatrix.d = -1/height*2; this.projectionMatrix.tx = -1; - this.projectionMatrix.ty = -1; + this.projectionMatrix.ty = 1; gl.bindTexture(gl.TEXTURE_2D, this.texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width , height , 0, gl.RGBA, gl.UNSIGNED_BYTE, null); @@ -157,7 +157,7 @@ /** * Destroys the filter texture. -* +* * @method destroy */ RenderTarget.prototype.destroy = function() diff --git a/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js b/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js index a63f4d7..3f37b7d 100644 --- a/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js +++ b/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js @@ -183,7 +183,7 @@ */ WebGLFastSpriteBatch.prototype.begin = function (spriteBatch) { - this.shader = this.renderer.shaderManager.fastShader; + this.shader = this.renderer.shaderManager.plugins.fastShader; this.matrix = spriteBatch.worldTransform.toArray(true); diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index bf8db28..2dc47ff 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -201,8 +201,8 @@ Sprite.prototype._renderWebGL = function (renderer) { - renderer.setObjectRenderer(renderer.objectRenderers.sprite); - renderer.objectRenderers.sprite.render(this); + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); }; /** diff --git a/src/core/sprites/SpriteBatch.js b/src/core/sprites/SpriteBatch.js index 63cebc0..4fe33e4 100644 --- a/src/core/sprites/SpriteBatch.js +++ b/src/core/sprites/SpriteBatch.js @@ -61,7 +61,7 @@ renderer.spriteBatch.stop(); - renderer.shaderManager.setShader(renderer.shaderManager.fastShader); + renderer.shaderManager.setShader(renderer.shaderManager.plugins.fastShader); renderer.fastSpriteBatch.begin(this); renderer.fastSpriteBatch.render(this); diff --git a/src/core/sprites/SpriteRenderer.js b/src/core/sprites/SpriteRenderer.js deleted file mode 100644 index fd9a802..0000000 --- a/src/core/sprites/SpriteRenderer.js +++ /dev/null @@ -1,537 +0,0 @@ -var ObjectRenderer = require('../renderers/webgl/utils/ObjectRenderer'), - Shader = require('../renderers/webgl/shaders/Shader'), - SpriteShader = require('../renderers/webgl/shaders/SpriteShader'), - WebGLRenderer = require('../renderers/webgl/WebGLRenderer'); - math = require('../math'); - -/** - * @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.setupContext(); -} - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; -module.exports = 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 = sprite; - 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 - gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, this.renderer.currentRenderTarget.projectionMatrix.toArray(true)); - } - } - - 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; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js new file mode 100644 index 0000000..c006ba4 --- /dev/null +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -0,0 +1,533 @@ +var ObjectRenderer = require('../../renderers/webgl/utils/ObjectRenderer'), + Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLRenderer = require('../../renderers/webgl/WebGLRenderer'), + SpriteShader = require('./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) +{ + 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; + + this.setupContext(); +} + +SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); +SpriteRenderer.prototype.constructor = SpriteRenderer; +module.exports = SpriteRenderer; + +WebGLRenderer.registerPlugin('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(this.renderer.shaderManager); + + // 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(this.renderer.shaderManager, 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 + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, this.renderer.currentRenderTarget.projectionMatrix.toArray(true)); + } + } + + 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; +}; diff --git a/src/core/sprites/webgl/SpriteShader.js b/src/core/sprites/webgl/SpriteShader.js new file mode 100644 index 0000000..3925e9f --- /dev/null +++ b/src/core/sprites/webgl/SpriteShader.js @@ -0,0 +1,45 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function SpriteShader(shaderManager) +{ + Shader.call(this, + shaderManager, + null, + // fragment shader + [ + 'precision lowp float;', + + 'varying vec2 vTextureCoord;', + 'varying vec4 vColor;', + + 'uniform sampler2D uSampler;', + + 'void main(void){', + ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aTextureCoord: 0, + aColor: 0 + } + ); +} + +SpriteShader.prototype = Object.create(Shader.prototype); +SpriteShader.prototype.constructor = SpriteShader; +module.exports = SpriteShader; + +WebGLShaderManager.registerPlugin('fastShader', SpriteShader); diff --git a/src/core/utils/index.js b/src/core/utils/index.js index bfae7ae..1d451c0 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -7,9 +7,10 @@ _uid: 0, _saidHello: false, - PolyK: require('./PolyK'), - EventData: require('./EventData'), - eventTarget: require('./eventTarget'), + PolyK: require('./PolyK'), + EventData: require('./EventData'), + eventTarget: require('./eventTarget'), + pluginTarget: require('./pluginTarget'), /** * Gets the next uuid diff --git a/src/core/utils/pluginTarget.js b/src/core/utils/pluginTarget.js new file mode 100644 index 0000000..885fe0e --- /dev/null +++ b/src/core/utils/pluginTarget.js @@ -0,0 +1,37 @@ +/** + * Mixins functionality to make an object have "plugins". + * + * @mixin + * @namespace PIXI + * @param obj {object} The object to mix into. + * @example + * function MyObject() {} + * + * pluginTarget.mixin(MyObject); + */ +function pluginTarget(obj) { + obj.__plugins = {}; + + obj.registerPlugin = function (pluginName, ctor) { + obj.__plugins[pluginName] = ctor; + }; + + obj.prototype.initPlugins = function () { + this.plugins = {}; + + for (var o in obj.__plugins) { + this.plugins[o] = new (obj.__plugins[o])(this); + } + }; + + obj.prototype.destroyPlugins = function () { + for (var o in this.plugins) { + this.plugins[o].destroy(); + this.plugins[o] = null; + } + + this.plugins = null; + }; +} + +module.exports = pluginTarget; diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js new file mode 100644 index 0000000..41be102 --- /dev/null +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -0,0 +1,48 @@ +/** + * @class + * @private + */ +function WebGLGraphicsData(gl) { + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +} + +WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; +module.exports = WebGLGraphicsData; + +/** + * + */ +WebGLGraphicsData.prototype.reset = function () { + this.points = []; + this.indices = []; +}; + +/** + * + */ +WebGLGraphicsData.prototype.upload = function () { + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; diff --git a/src/core/index.js b/src/core/index.js index 6f744e9..1d0a398 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -23,12 +23,12 @@ Sprite: require('./sprites/Sprite'), SpriteBatch: require('./sprites/SpriteBatch'), - SpriteRenderer: require('./sprites/SpriteRenderer'), + SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), // primitives - Graphics: require('./primitives/Graphics'), - GraphicsData: require('./primitives/GraphicsData'), - GraphicsRenderer: require('./primitives/GraphicsRenderer'), + Graphics: require('./graphics/Graphics'), + GraphicsData: require('./graphics/GraphicsData'), + GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,10 +42,8 @@ CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), // renderers - webgl - WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), - // WebGLGraphics: require('./renderers/webgl/utils/WebGLGraphics'), - - + WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), + Shader: require('./renderers/webgl/shaders/Shader'), /** * This helper function will automatically detect which renderer you should be using. @@ -103,7 +101,3 @@ return core.autoDetectRenderer(width, height, options, isAndroid); } }; - -// Adding here for now.. sure there is a better place! -core.WebGLRenderer.registerObjectRenderer('sprite', core.SpriteRenderer); -core.WebGLRenderer.registerObjectRenderer('graphics', core.GraphicsRenderer); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index a086950..81de05c 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -268,6 +268,6 @@ matrix.ty = this.ty; return matrix; -} +}; Matrix.IDENTITY = new Matrix(); diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js deleted file mode 100644 index 8db9b45..0000000 --- a/src/core/primitives/Graphics.js +++ /dev/null @@ -1,1116 +0,0 @@ -var DisplayObjectContainer = require('../display/DisplayObjectContainer'), - //Sprite = require('../display/Sprite'), - Texture = require('../textures/Texture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), - // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), - GraphicsData = require('./GraphicsData'), - math = require('../math'), - CONST = require('../const'); - -/** - * The Graphics class contains methods used to draw primitive shapes such as lines, circles and - * rectangles to the display, and color and fill them. - * - * @class - * @extends DisplayObjectContainer - * @namespace PIXI - */ -function Graphics() -{ - DisplayObjectContainer.call(this); - - this.renderable = true; - - /** - * The alpha value used when filling the Graphics object. - * - * @member {number} - * @default 1 - */ - this.fillAlpha = 1; - - /** - * The width (thickness) of any lines drawn. - * - * @member {number} - * @default 0 - */ - this.lineWidth = 0; - - /** - * The color of any lines drawn. - * - * @member {string} - * @default 0 - */ - this.lineColor = 0; - - /** - * Graphics data - * - * @member {GraphicsData[]} - * @private - */ - this.graphicsData = []; - - /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * Current path - * - * @member {GraphicsData} - * @private - */ - this.currentPath = null; - - /** - * Array containing some WebGL-related properties used by the WebGL renderer. - * - * @member {object} - * @private - */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; - - /** - * Whether this shape is being used as a mask. - * - * @member {boolean} - */ - this.isMask = false; - - /** - * The bounds' padding used for bounds calculation. - * - * @member {number} - */ - this.boundsPadding = 0; - - /** - * A cache of the local bounds to prevent recalculation. - * - * @member {Rectangle} - * @private - */ - this._localBounds = new math.Rectangle(0,0,1,1); - - /** - * Used to detect if the graphics object has changed. If this is set to true then the graphics - * object will be recalculated. - * - * @member {boolean} - * @private - */ - this.dirty = true; - - /** - * Used to detect if the WebGL graphics object has changed. If this is set to true then the - * graphics object will be recalculated. - * - * @member {boolean} - * @private - */ - this.glDirty = false; - - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; -} - -// constructor -Graphics.prototype = Object.create(DisplayObjectContainer.prototype); -Graphics.prototype.constructor = Graphics; -module.exports = Graphics; - -Object.defineProperties(Graphics.prototype, { - /** - * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. - * This is useful if your graphics element does not change often, as it will speed up the rendering - * of the object in exchange for taking up texture memory. It is also useful if you need the graphics - * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if - * you are constantly redrawing the graphics element. - * - * @member {boolean} - * @memberof Graphics# - * @default false - * @private - */ - cacheAsBitmap: { - get: function () - { - return this._cacheAsBitmap; - }, - set: function (value) - { - this._cacheAsBitmap = value; - - if (this._cacheAsBitmap) - { - this._generateCachedSprite(); - } - else - { - this.destroyCachedSprite(); - this.dirty = true; - } - } - } -}); - -/** - * Creates a new Graphics object with the same values as this one. - * - * @return {Graphics} - */ -GraphicsData.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = this.dirty; - clone.glDirty = this.glDirty; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData.clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {Graphics} - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (arguments.length < 3) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); - } - else - { - // otherwise its empty so lets just set the line properties - this.currentPath.lineWidth = this.lineWidth; - this.currentPath.lineColor = this.lineColor; - this.currentPath.lineAlpha = this.lineAlpha; - } - } - - return this; -}; - -/** - * Moves the current drawing position to x, y. - * - * @param x {number} the X coordinate to move to - * @param y {number} the Y coordinate to move to - * @return {Graphics} - */ -Graphics.prototype.moveTo = function (x, y) -{ - this.drawShape(new math.Polygon([x,y])); - - return this; -}; - -/** - * Draws a line using the current line style from the current drawing position to (x, y); - * The current drawing position is then set to (x, y). - * - * @param x {number} the X coordinate to draw to - * @param y {number} the Y coordinate to draw to - * @return {Graphics} - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a quadratic bezier curve and then draws it. - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @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 {Graphics} - */ -Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var xa, - ya, - n = 20, - points = this.currentPath.shape.points; - - if (points.length === 0) - { - this.moveTo(0, 0); - } - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - for (var i = 1; i <= n; ++i) - { - j = i / n; - - xa = fromX + ( (cpX - fromX) * j ); - ya = fromY + ( (cpY - fromY) * j ); - - points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), - ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); - } - - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a bezier curve and then draws it. - * - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param cpX2 {number} Second Control point x - * @param cpY2 {number} Second Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {Graphics} - */ -Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var n = 20, - dt, - dt2, - dt3, - t2, - t3, - points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - - for (var i = 1; i <= n; ++i) - { - j = i / n; - - dt = (1 - j); - dt2 = dt * dt; - dt3 = dt2 * dt; - - t2 = j * j; - t3 = t2 * j; - - points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, - dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); - } - - this.dirty = true; - - return this; -}; - -/** - * The arcTo() method creates an arc/curve between two tangents on the canvas. - * - * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! - * - * @param x1 {number} The x-coordinate of the beginning of the arc - * @param y1 {number} The y-coordinate of the beginning of the arc - * @param x2 {number} The x-coordinate of the end of the arc - * @param y2 {number} The y-coordinate of the end of the arc - * @param radius {number} The radius of the arc - * @return {Graphics} - */ -Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points.push(x1, y1); - } - } - else - { - this.moveTo(x1, y1); - } - - var points = this.currentPath.shape.points, - fromX = points[points.length-2], - fromY = points[points.length-1], - a1 = fromY - y1, - b1 = fromX - x1, - a2 = y2 - y1, - b2 = x2 - x1, - mm = Math.abs(a1 * b2 - b1 * a2); - - if (mm < 1.0e-8 || radius === 0) - { - if (points[points.length-2] !== x1 || points[points.length-1] !== y1) - { - points.push(x1, y1); - } - } - else - { - var dd = a1 * a1 + b1 * b1, - cc = a2 * a2 + b2 * b2, - tt = a1 * a2 + b1 * b2, - k1 = radius * Math.sqrt(dd) / mm, - k2 = radius * Math.sqrt(cc) / mm, - j1 = k1 * tt / dd, - j2 = k2 * tt / cc, - cx = k1 * b2 + k2 * b1, - cy = k1 * a2 + k2 * a1, - px = b1 * (k2 + j1), - py = a1 * (k2 + j1), - qx = b2 * (k1 + j2), - qy = a2 * (k1 + j2), - startAngle = Math.atan2(py - cy, px - cx), - endAngle = Math.atan2(qy - cy, qx - cx); - - this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); - } - - this.dirty = true; - - return this; -}; - -/** - * The arc method creates an arc/curve (used to create circles, or parts of circles). - * - * @param cx {number} The x-coordinate of the center of the circle - * @param cy {number} The y-coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) - * @param endAngle {number} The ending angle, in radians - * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {Graphics} - */ -Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - var points; - - // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... - if (this.currentPath) - { - points = this.currentPath.shape.points; - - if (points.length === 0) - { - points.push(startX, startY); - } - else if (points[points.length-2] !== startX || points[points.length-1] !== startY) - { - points.push(startX, startY); - } - } - else - { - this.moveTo(startX, startY); - points = this.currentPath.shape.points; - } - - if (startAngle === endAngle) - { - return this; - } - - if (!anticlockwise && endAngle <= startAngle) - { - endAngle += Math.PI * 2; - } - else if (anticlockwise && startAngle <= endAngle) - { - startAngle += Math.PI * 2; - } - - var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); - var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; - - if (sweep === 0) - { - return this; - } - - var theta = sweep/(segs*2); - var theta2 = theta*2; - - var cTheta = Math.cos(theta); - var sTheta = Math.sin(theta); - - var segMinus = segs - 1; - - var remainder = ( segMinus % 1 ) / segMinus; - - for (var i = 0; i <= segMinus; ++i) - { - var real = i + remainder * i; - var angle = ((theta) + startAngle + (theta2 * real)); - - var c = Math.cos(angle); - var s = -Math.sin(angle); - - points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, - ( (cTheta * -s) + (sTheta * c) ) * radius + cy); - } - - this.dirty = true; - - return this; -}; - -/** - * Specifies a simple one-color fill that subsequent calls to other Graphics methods - * (such as lineTo() or drawCircle()) use when drawing. - * - * @param color {number} the color of the fill - * @param alpha {number} the alpha of the fill - * @return {Graphics} - */ -Graphics.prototype.beginFill = function (color, alpha) -{ - this.filling = true; - this.fillColor = color || 0; - this.fillAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length <= 2) - { - this.currentPath.fill = this.filling; - this.currentPath.fillColor = this.fillColor; - this.currentPath.fillAlpha = this.fillAlpha; - } - } - return this; -}; - -/** - * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. - * - * @return {Graphics} - */ -Graphics.prototype.endFill = function () -{ - this.filling = false; - this.fillColor = null; - this.fillAlpha = 1; - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @return {Graphics} - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {Graphics} - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {Graphics} - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {Array} The path data used to construct the polygon. - * @return {Graphics} - */ -Graphics.prototype.drawPolygon = function (path) -{ - if (!(path instanceof Array)) - { - path = Array.prototype.slice.call(arguments); - } - - this.drawShape(new math.Polygon(path)); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {Graphics} - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty = true; - this.clearDirty = true; - this.graphicsData = []; - - return this; -}; - -/** - * Useful function that returns a texture of the graphics object that can then be used to create sprites - * This can be quite useful if your geometry is complicated and needs to be reused multiple times. - * - * @param resolution {number} The resolution of the texture being generated - * @param scaleMode {number} Should be one of the scaleMode consts - * @return {Texture} a texture of the graphics object - */ -Graphics.prototype.generateTexture = function (resolution, scaleMode) -{ - resolution = resolution || 1; - - var bounds = this.getBounds(); - - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); - - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - canvasBuffer.context.scale(resolution, resolution); - - canvasBuffer.context.translate(-bounds.x,-bounds.y); - - CanvasGraphics.renderGraphics(this, canvasBuffer.context); - - return texture; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (this.isMask === true) - { - return; - } - - // this code may still be needed so leaving for now.. - // - /* - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture on the gpu too! - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.worldAlpha = this.worldAlpha; - - Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); - - return; - } - - */ - - if (this.glDirty) - { - this.dirty = true; - this.glDirty = false; - } - - renderer.setObjectRenderer(renderer.objectRenderers.graphics); - renderer.objectRenderers.graphics.render(this); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} - * @private - */ -Graphics.prototype.renderCanvas = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.alpha <= 0 || this.isMask === true) - { - return; - } - - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.alpha = this.alpha; - - Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); - - return; - } - else - { - var context = renderer.context; - var transform = this.worldTransform; - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - var resolution = renderer.resolution; - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } - } -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @return {Rectangle} the rectangular bounding area - */ -Graphics.prototype.getBounds = function (matrix) -{ - // return an empty object if the item is a mask! - if (this.isMask) - { - return math.Rectangle.EMPTY; - } - - if (this.dirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.dirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - return this._bounds; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; - - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; -}; - -/** - * Generates the cached sprite when the sprite has cacheAsBitmap = true - * - * @private - */ -Graphics.prototype._generateCachedSprite = function () -{ - var bounds = this.getLocalBounds(); - - if (!this._cachedSprite) - { - var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); - var texture = Texture.fromCanvas(canvasBuffer.canvas); - - this._cachedSprite = new Sprite(texture); - this._cachedSprite.buffer = canvasBuffer; - - this._cachedSprite.worldTransform = this.worldTransform; - } - else - { - this._cachedSprite.buffer.resize(bounds.width, bounds.height); - } - - // leverage the anchor to account for the offset of the element - this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); - this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); - - // this._cachedSprite.buffer.context.save(); - this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); - - // make sure we set the alpha of the graphics to 1 for the render.. - this.worldAlpha = 1; - - // now render the graphic.. - CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); - - this._cachedSprite.alpha = this.alpha; -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Graphics.prototype.updateCachedSpriteTexture = function () -{ - var cachedSprite = this._cachedSprite; - var texture = cachedSprite.texture; - var canvas = cachedSprite.buffer.canvas; - - texture.baseTexture.width = canvas.width; - texture.baseTexture.height = canvas.height; - texture.crop.width = texture.frame.width = canvas.width; - texture.crop.height = texture.frame.height = canvas.height; - - cachedSprite._width = canvas.width; - cachedSprite._height = canvas.height; - - // update the dirty base textures - texture.baseTexture.dirty(); -}; - -/** - * Destroys a previous cached sprite. - * - */ -Graphics.prototype.destroyCachedSprite = function () -{ - this._cachedSprite.texture.destroy(true); - - // let the gc collect the unused sprite - // TODO could be object pooled! - this._cachedSprite = null; -}; - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. - * @return {GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = this.filling; - this.currentPath = data; - } - - this.dirty = true; - - return data; -}; diff --git a/src/core/primitives/GraphicsData.js b/src/core/primitives/GraphicsData.js deleted file mode 100644 index 788f6f2..0000000 --- a/src/core/primitives/GraphicsData.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * A GraphicsData object. - * - * @class - * @namespace PIXI - */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - this.lineWidth = lineWidth; - this.lineColor = lineColor; - this.lineAlpha = lineAlpha; - this._lineTint = lineColor; - - this.fillColor = fillColor; - this.fillAlpha = fillAlpha; - this._fillTint = fillColor; - this.fill = fill; - - this.shape = shape; - this.type = shape.type; -} - -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {GraphicsData} - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js deleted file mode 100644 index 1a508f9..0000000 --- a/src/core/primitives/GraphicsRenderer.js +++ /dev/null @@ -1,874 +0,0 @@ -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; - -/** - * 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 ); - -// var matrix = graphics.worldTransform.clone(); -// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); -// matrix.append(graphics.worldTransform); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.maskManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.maskManager.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.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); - - 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/primitives/WebGLGraphicsData.js b/src/core/primitives/WebGLGraphicsData.js deleted file mode 100755 index 41be102..0000000 --- a/src/core/primitives/WebGLGraphicsData.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @class - * @private - */ -function WebGLGraphicsData(gl) { - this.gl = gl; - - //TODO does this need to be split before uploding?? - this.color = [0,0,0]; // color split! - this.points = []; - this.indices = []; - this.buffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - this.mode = 1; - this.alpha = 1; - this.dirty = true; -} - -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; -module.exports = WebGLGraphicsData; - -/** - * - */ -WebGLGraphicsData.prototype.reset = function () { - this.points = []; - this.indices = []; -}; - -/** - * - */ -WebGLGraphicsData.prototype.upload = function () { - var gl = this.gl; - -// this.lastIndex = graphics.graphicsData.length; - this.glPoints = new Float32Array(this.points); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); - - this.glIndicies = new Uint16Array(this.indices); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); - - this.dirty = false; -}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6c1fb7d..4e82baa 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -174,6 +174,9 @@ // time to create the render managers! each one focuses on managing a state in webGL + // initialize the context so it is ready for the managers. + this._initContext(); + /** * Deals with managing the shader programs and their attribs * @member {WebGLShaderManager} @@ -204,17 +207,11 @@ */ this.blendModeManager = new WebGLBlendModeManager(this); - - this.blendModes = null; this._boundUpdateTexture = this.updateTexture.bind(this); this._boundDestroyTexture = this.destroyTexture.bind(this); - - - // time init the context.. - this._initContext(); this.currentRenderTarget = this.renderTarget; @@ -228,27 +225,18 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - /** - * Manages the renderer of specific objects. - * - * @member {object} - */ - this.objectRenderers = {}; - this.currentRenderer = new ObjectRenderer(); - - // create an instance of each registered object renderer - for (var o in WebGLRenderer._objectRenderers) { - this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); - } - + this.initPlugins(); } // constructor WebGLRenderer.prototype.constructor = WebGLRenderer; module.exports = WebGLRenderer; +WebGLRenderer.glContextId = 0; + +utils.pluginTarget.mixin(WebGLRenderer); utils.eventTarget.mixin(WebGLRenderer.prototype); Object.defineProperties(WebGLRenderer.prototype, { @@ -297,11 +285,9 @@ gl.enable(gl.BLEND); this.renderTarget = new RenderTarget(this.gl, this.width, this.height, null, true); - this.emit('context', gl); - // now resize and we are good to go! this.resize(this.width, this.height); }; @@ -349,7 +335,7 @@ gl.clear(gl.COLOR_BUFFER_BIT); } - this.renderDisplayObject(object, this.renderTarget)//this.projection); + this.renderDisplayObject(object, this.renderTarget);//this.projection); }; /** @@ -536,12 +522,7 @@ this.maskManager.destroy(); this.filterManager.destroy(); - for (var o in this.objectRenderers) { - this.objectRenderers[o].destroy(); - this.objectRenderers[o] = null; - } - - this.objectRenderers = null; + this.destroyPlugins(); // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -611,11 +592,3 @@ this.blendModes[CONST.blendModes.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; } }; - -WebGLRenderer.glContextId = 0; -WebGLRenderer._objectRenderers = {}; - -WebGLRenderer.registerObjectRenderer = function (name, ctor) { - WebGLRenderer._objectRenderers[name] = ctor; -}; - diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js index 87c99f0..d6ed3c6 100644 --- a/src/core/renderers/webgl/managers/WebGLFilterManager.js +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -51,7 +51,7 @@ */ WebGLFilterManager.prototype.begin = function (buffer) { - this.defaultShader = this.renderer.shaderManager.defaultShader; + this.defaultShader = this.renderer.shaderManager.plugins.defaultShader; this.width = this.renderer.projection.x * 2; this.height = -this.renderer.projection.y * 2; @@ -385,7 +385,7 @@ if (!shader) { - shader = new Shader(gl); + shader = new Shader(this.renderer.shaderManager); shader.fragmentSrc = filter.fragmentSrc; shader.uniforms = filter.uniforms; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js index 6a1f782..d6a11c1 100644 --- a/src/core/renderers/webgl/managers/WebGLMaskManager.js +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -126,11 +126,11 @@ // bind the graphics object.. var projection = this.renderer.projection, offset = this.renderer.offset, - shader;// = this.renderer.shaderManager.primitiveShader; + shader;// = this.renderer.shaderManager.plugins.primitiveShader; if (webGLData.mode === 1) { - shader = this.renderer.shaderManager.complexPrimitiveShader; + shader = this.renderer.shaderManager.plugins.complexPrimitiveShader; this.renderer.shaderManager.setShader(shader); @@ -156,7 +156,7 @@ else { //this.renderer.shaderManager.activatePrimitiveShader(); - shader = this.renderer.shaderManager.primitiveShader; + shader = this.renderer.shaderManager.plugins.primitiveShader; this.renderer.shaderManager.setShader( shader ); gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); @@ -292,11 +292,11 @@ */ WebGLMaskManager.prototype.pushMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); if (maskData.dirty) { - this.renderer.objectRenderers.graphics.updateGraphics(maskData, this.renderer.gl); + this.renderer.plugins.graphics.updateGraphics(maskData, this.renderer.gl); } if (!maskData._webGL[this.renderer.gl.id].data.length) @@ -314,7 +314,7 @@ */ WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); this.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); }; diff --git a/src/core/renderers/webgl/managers/WebGLShaderManager.js b/src/core/renderers/webgl/managers/WebGLShaderManager.js index 90c6477..9ebb58e 100644 --- a/src/core/renderers/webgl/managers/WebGLShaderManager.js +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -1,9 +1,5 @@ var WebGLManager = require('./WebGLManager'), - PrimitiveShader = require('../shaders/PrimitiveShader'), - ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), - Shader = require('../shaders/Shader'), - FastShader = require('../shaders/FastShader'), - StripShader = require('../shaders/StripShader'); + utils = require('../../../utils'); /** * @class @@ -51,41 +47,16 @@ */ this.currentShader = null; - // this shader is used for rendering primitives - this.primitiveShader = null; - - // this shader is used for rendering triangle strips - this.complexPrimitiveShader = null; - - // this shader is used for the default sprite rendering - this.defaultShader = null; - - // this shader is used for the fast sprite rendering - this.fastShader = null; - - // the next one is used for rendering triangle strips - this.stripShader = null; + this.initPlugins(); // listen for context and update necessary shaders var self = this; - this.renderer.on('context', function (event) + this.renderer.on('context', function () { - var gl = event.data; - - // this shader is used for rendering primitives - self.primitiveShader = new PrimitiveShader(gl); - - // this shader is used for rendering triangle strips - self.complexPrimitiveShader = new ComplexPrimitiveShader(gl); - - // this shader is used for the default sprite rendering - self.defaultShader = new Shader(gl); - - // this shader is used for the fast sprite rendering - self.fastShader = new FastShader(gl); - - // the next one is used for rendering triangle strips - self.stripShader = new StripShader(gl); + for (var o in this.plugins) + { + this.plugins[o] = new (this.plugins[o].constructor)(self); + } self.setShader(self.defaultShader); }); @@ -95,6 +66,8 @@ WebGLShaderManager.prototype.constructor = WebGLShaderManager; module.exports = WebGLShaderManager; +utils.pluginTarget.mixin(WebGLShaderManager); + /** * Takes the attributes given in parameters. * @@ -164,24 +137,11 @@ */ WebGLShaderManager.prototype.destroy = function () { + this.destroyPlugins(); + this.attribState = null; this.tempAttribState = null; - this.primitiveShader.destroy(); - this.primitiveShader = null; - - this.complexPrimitiveShader.destroy(); - this.complexPrimitiveShader = null; - - this.defaultShader.destroy(); - this.defaultShader = null; - - this.fastShader.destroy(); - this.fastShader = null; - - this.stripShader.destroy(); - this.stripShader = null; - this.renderer = null; }; diff --git a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js b/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js deleted file mode 100644 index f8a07da..0000000 --- a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js +++ /dev/null @@ -1,53 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function ComplexPrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform vec3 tint;', - 'uniform float alpha;', - 'uniform vec3 color;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - color: { type: '3f', value: [0,0,0] }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); -ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; -module.exports = ComplexPrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/FastShader.js b/src/core/renderers/webgl/shaders/FastShader.js index d198f10..c80d040 100644 --- a/src/core/renderers/webgl/shaders/FastShader.js +++ b/src/core/renderers/webgl/shaders/FastShader.js @@ -1,15 +1,16 @@ -var Shader = require('./Shader'); +var Shader = require('./Shader'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @extends Shader * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. */ -function FastShader(gl) +function FastShader(shaderManager) { Shader.call(this, - gl, + shaderManager, // vertex shader [ 'attribute vec2 aVertexPosition;', @@ -70,3 +71,5 @@ FastShader.prototype = Object.create(Shader.prototype); FastShader.prototype.constructor = FastShader; module.exports = FastShader; + +WebGLShaderManager.registerPlugin('fastShader', FastShader); diff --git a/src/core/renderers/webgl/shaders/PrimitiveShader.js b/src/core/renderers/webgl/shaders/PrimitiveShader.js deleted file mode 100644 index 753c475..0000000 --- a/src/core/renderers/webgl/shaders/PrimitiveShader.js +++ /dev/null @@ -1,55 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform float alpha;', - 'uniform float flipY;', - 'uniform vec3 tint;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aColor:0 - } - ); -} - -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; -module.exports = PrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/Shader.js b/src/core/renderers/webgl/shaders/Shader.js index d065a76..c60c5f9 100644 --- a/src/core/renderers/webgl/shaders/Shader.js +++ b/src/core/renderers/webgl/shaders/Shader.js @@ -1,12 +1,16 @@ -var utils = require('../../../utils'); +var utils = require('../../../utils'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @namespace PIXI - * @param [fragmentSrc] {string} The source of the fragment shader. + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. * @param [vertexSrc] {string} The source of the vertex shader. + * @param [fragmentSrc] {string} The source of the fragment shader. + * @param customUniforms {object} Custom uniforms to use to augment the built-in ones. + * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Shader(gl, vertexSrc, fragmentSrc, customUniforms, customAttributes) +function Shader(shaderManager, vertexSrc, fragmentSrc, customUniforms, customAttributes) { /** * @member {number} @@ -18,7 +22,7 @@ * @member {WebGLContext} * @readonly */ - this.gl = gl; + this.gl = shaderManager.renderer.gl; /** * The WebGL program. @@ -31,8 +35,8 @@ uSampler: { type: 'sampler2D', value: 0 }, projectionVector: { type: '2f', value: { x: 0, y: 0 } }, offsetVector: { type: '2f', value: { x: 0, y: 0 } }, - projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, - 0, 1, 0, + projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, + 0, 1, 0, 0, 0, 1) }, dimensions: { type: '4f', value: new Float32Array(4) } }; @@ -103,6 +107,8 @@ Shader.prototype.constructor = Shader; module.exports = Shader; +WebGLShaderManager.registerPlugin('defaultShader', Shader); + Shader.prototype.init = function () { this.compile(); @@ -506,4 +512,4 @@ } return shader; -}; \ No newline at end of file +}; diff --git a/src/core/renderers/webgl/shaders/SpriteShader.js b/src/core/renderers/webgl/shaders/SpriteShader.js deleted file mode 100644 index 1805b0a..0000000 --- a/src/core/renderers/webgl/shaders/SpriteShader.js +++ /dev/null @@ -1,42 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function SpriteShader(gl) -{ - Shader.call(this, - gl, - null, - // fragment shader - [ - 'precision lowp float;', - - 'varying vec2 vTextureCoord;', - 'varying vec4 vColor;', - - 'uniform sampler2D uSampler;', - - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aTextureCoord: 0, - aColor: 0 - } - ); -} - -SpriteShader.prototype = Object.create(Shader.prototype); -SpriteShader.prototype.constructor = SpriteShader; -module.exports = SpriteShader; diff --git a/src/core/renderers/webgl/shaders/StripShader.js b/src/core/renderers/webgl/shaders/StripShader.js deleted file mode 100644 index f487381..0000000 --- a/src/core/renderers/webgl/shaders/StripShader.js +++ /dev/null @@ -1,54 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function StripShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - // 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform vec2 projectionVector;', - 'uniform vec2 offsetVector;', - - 'varying vec2 vTextureCoord;', - - 'void main(void){', - ' vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);', - ' v -= offsetVector.xyx;', - ' gl_Position = vec4( v.x / projectionVector.x -1.0, v.y / -projectionVector.y + 1.0 , 0.0, 1.0);', - ' vTextureCoord = aTextureCoord;', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'uniform float alpha;', - 'uniform sampler2D uSampler;', - - 'varying vec2 vTextureCoord;', - - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y)) * alpha;', - '}' - ].join('\n'), - // custom uniforms - { - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -StripShader.prototype = Object.create(Shader.prototype); -StripShader.prototype.constructor = StripShader; -module.exports = StripShader; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index 0a34e55..43e2d00 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -33,7 +33,7 @@ * @property texture * @type Any */ - this.texture = null + this.texture = null; this.width = 0; this.height = 0; @@ -56,11 +56,11 @@ this.frameBuffer = gl.createFramebuffer(); - /* + /* A frame buffer needs a target to render to.. create a texture and bind it attach it to the framebuffer.. */ - + this.texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, this.texture); @@ -70,13 +70,13 @@ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); 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); - + gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer ); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0); - - /* + + /* The stencil buffer is used for masking in pixi lets create one and then add attach it to the framebuffer.. */ @@ -86,7 +86,7 @@ } this.resize(width, height); - + }; RenderTarget.prototype.constructor = RenderTarget; @@ -94,21 +94,21 @@ /** * Clears the filter texture. -* +* * @method clear */ RenderTarget.prototype.clear = function() { var gl = this.gl; - + gl.clearColor(0,0,0, 0); gl.clear(gl.COLOR_BUFFER_BIT); }; RenderTarget.prototype.activate = function() { - this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer ); -} + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer); +}; /** * Resizes the texture to the specified width and height @@ -119,25 +119,25 @@ */ RenderTarget.prototype.resize = function(width, height) { - width = width | 0; - height = height | 0; - - if(this.width === width && this.height === height) return; + if (this.width === width && this.height === height) { + return; + } this.width = width; this.height = height; + this.projectionMatrix = new math.Matrix(); - - if(!this.root) + + if (!this.root) { var gl = this.gl; this.projectionMatrix.a = 1/width*2; - this.projectionMatrix.d = 1/height*2; + this.projectionMatrix.d = -1/height*2; this.projectionMatrix.tx = -1; - this.projectionMatrix.ty = -1; + this.projectionMatrix.ty = 1; gl.bindTexture(gl.TEXTURE_2D, this.texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width , height , 0, gl.RGBA, gl.UNSIGNED_BYTE, null); @@ -157,7 +157,7 @@ /** * Destroys the filter texture. -* +* * @method destroy */ RenderTarget.prototype.destroy = function() diff --git a/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js b/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js index a63f4d7..3f37b7d 100644 --- a/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js +++ b/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js @@ -183,7 +183,7 @@ */ WebGLFastSpriteBatch.prototype.begin = function (spriteBatch) { - this.shader = this.renderer.shaderManager.fastShader; + this.shader = this.renderer.shaderManager.plugins.fastShader; this.matrix = spriteBatch.worldTransform.toArray(true); diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index bf8db28..2dc47ff 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -201,8 +201,8 @@ Sprite.prototype._renderWebGL = function (renderer) { - renderer.setObjectRenderer(renderer.objectRenderers.sprite); - renderer.objectRenderers.sprite.render(this); + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); }; /** diff --git a/src/core/sprites/SpriteBatch.js b/src/core/sprites/SpriteBatch.js index 63cebc0..4fe33e4 100644 --- a/src/core/sprites/SpriteBatch.js +++ b/src/core/sprites/SpriteBatch.js @@ -61,7 +61,7 @@ renderer.spriteBatch.stop(); - renderer.shaderManager.setShader(renderer.shaderManager.fastShader); + renderer.shaderManager.setShader(renderer.shaderManager.plugins.fastShader); renderer.fastSpriteBatch.begin(this); renderer.fastSpriteBatch.render(this); diff --git a/src/core/sprites/SpriteRenderer.js b/src/core/sprites/SpriteRenderer.js deleted file mode 100644 index fd9a802..0000000 --- a/src/core/sprites/SpriteRenderer.js +++ /dev/null @@ -1,537 +0,0 @@ -var ObjectRenderer = require('../renderers/webgl/utils/ObjectRenderer'), - Shader = require('../renderers/webgl/shaders/Shader'), - SpriteShader = require('../renderers/webgl/shaders/SpriteShader'), - WebGLRenderer = require('../renderers/webgl/WebGLRenderer'); - math = require('../math'); - -/** - * @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.setupContext(); -} - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; -module.exports = 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 = sprite; - 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 - gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, this.renderer.currentRenderTarget.projectionMatrix.toArray(true)); - } - } - - 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; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js new file mode 100644 index 0000000..c006ba4 --- /dev/null +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -0,0 +1,533 @@ +var ObjectRenderer = require('../../renderers/webgl/utils/ObjectRenderer'), + Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLRenderer = require('../../renderers/webgl/WebGLRenderer'), + SpriteShader = require('./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) +{ + 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; + + this.setupContext(); +} + +SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); +SpriteRenderer.prototype.constructor = SpriteRenderer; +module.exports = SpriteRenderer; + +WebGLRenderer.registerPlugin('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(this.renderer.shaderManager); + + // 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(this.renderer.shaderManager, 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 + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, this.renderer.currentRenderTarget.projectionMatrix.toArray(true)); + } + } + + 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; +}; diff --git a/src/core/sprites/webgl/SpriteShader.js b/src/core/sprites/webgl/SpriteShader.js new file mode 100644 index 0000000..3925e9f --- /dev/null +++ b/src/core/sprites/webgl/SpriteShader.js @@ -0,0 +1,45 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function SpriteShader(shaderManager) +{ + Shader.call(this, + shaderManager, + null, + // fragment shader + [ + 'precision lowp float;', + + 'varying vec2 vTextureCoord;', + 'varying vec4 vColor;', + + 'uniform sampler2D uSampler;', + + 'void main(void){', + ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aTextureCoord: 0, + aColor: 0 + } + ); +} + +SpriteShader.prototype = Object.create(Shader.prototype); +SpriteShader.prototype.constructor = SpriteShader; +module.exports = SpriteShader; + +WebGLShaderManager.registerPlugin('fastShader', SpriteShader); diff --git a/src/core/utils/index.js b/src/core/utils/index.js index bfae7ae..1d451c0 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -7,9 +7,10 @@ _uid: 0, _saidHello: false, - PolyK: require('./PolyK'), - EventData: require('./EventData'), - eventTarget: require('./eventTarget'), + PolyK: require('./PolyK'), + EventData: require('./EventData'), + eventTarget: require('./eventTarget'), + pluginTarget: require('./pluginTarget'), /** * Gets the next uuid diff --git a/src/core/utils/pluginTarget.js b/src/core/utils/pluginTarget.js new file mode 100644 index 0000000..885fe0e --- /dev/null +++ b/src/core/utils/pluginTarget.js @@ -0,0 +1,37 @@ +/** + * Mixins functionality to make an object have "plugins". + * + * @mixin + * @namespace PIXI + * @param obj {object} The object to mix into. + * @example + * function MyObject() {} + * + * pluginTarget.mixin(MyObject); + */ +function pluginTarget(obj) { + obj.__plugins = {}; + + obj.registerPlugin = function (pluginName, ctor) { + obj.__plugins[pluginName] = ctor; + }; + + obj.prototype.initPlugins = function () { + this.plugins = {}; + + for (var o in obj.__plugins) { + this.plugins[o] = new (obj.__plugins[o])(this); + } + }; + + obj.prototype.destroyPlugins = function () { + for (var o in this.plugins) { + this.plugins[o].destroy(); + this.plugins[o] = null; + } + + this.plugins = null; + }; +} + +module.exports = pluginTarget; diff --git a/src/extras/Strip.js b/src/extras/Strip.js index 57691b5..6bf78cb 100644 --- a/src/extras/Strip.js +++ b/src/extras/Strip.js @@ -89,7 +89,7 @@ this._initWebGL(renderer); } - renderer.shaderManager.setShader(renderer.shaderManager.stripShader); + renderer.shaderManager.setShader(renderer.shaderManager.plugins.stripShader); this._renderStrip(renderer); @@ -128,7 +128,7 @@ var gl = renderer.gl; var projection = renderer.projection, offset = renderer.offset, - shader = renderer.shaderManager.stripShader; + shader = renderer.shaderManager.plugins.stripShader; var drawMode = this.drawMode === Strip.DrawModes.TRIANGLE_STRIP ? gl.TRIANGLE_STRIP : gl.TRIANGLES; diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js new file mode 100644 index 0000000..41be102 --- /dev/null +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -0,0 +1,48 @@ +/** + * @class + * @private + */ +function WebGLGraphicsData(gl) { + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +} + +WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; +module.exports = WebGLGraphicsData; + +/** + * + */ +WebGLGraphicsData.prototype.reset = function () { + this.points = []; + this.indices = []; +}; + +/** + * + */ +WebGLGraphicsData.prototype.upload = function () { + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; diff --git a/src/core/index.js b/src/core/index.js index 6f744e9..1d0a398 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -23,12 +23,12 @@ Sprite: require('./sprites/Sprite'), SpriteBatch: require('./sprites/SpriteBatch'), - SpriteRenderer: require('./sprites/SpriteRenderer'), + SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), // primitives - Graphics: require('./primitives/Graphics'), - GraphicsData: require('./primitives/GraphicsData'), - GraphicsRenderer: require('./primitives/GraphicsRenderer'), + Graphics: require('./graphics/Graphics'), + GraphicsData: require('./graphics/GraphicsData'), + GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,10 +42,8 @@ CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), // renderers - webgl - WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), - // WebGLGraphics: require('./renderers/webgl/utils/WebGLGraphics'), - - + WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), + Shader: require('./renderers/webgl/shaders/Shader'), /** * This helper function will automatically detect which renderer you should be using. @@ -103,7 +101,3 @@ return core.autoDetectRenderer(width, height, options, isAndroid); } }; - -// Adding here for now.. sure there is a better place! -core.WebGLRenderer.registerObjectRenderer('sprite', core.SpriteRenderer); -core.WebGLRenderer.registerObjectRenderer('graphics', core.GraphicsRenderer); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index a086950..81de05c 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -268,6 +268,6 @@ matrix.ty = this.ty; return matrix; -} +}; Matrix.IDENTITY = new Matrix(); diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js deleted file mode 100644 index 8db9b45..0000000 --- a/src/core/primitives/Graphics.js +++ /dev/null @@ -1,1116 +0,0 @@ -var DisplayObjectContainer = require('../display/DisplayObjectContainer'), - //Sprite = require('../display/Sprite'), - Texture = require('../textures/Texture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), - // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), - GraphicsData = require('./GraphicsData'), - math = require('../math'), - CONST = require('../const'); - -/** - * The Graphics class contains methods used to draw primitive shapes such as lines, circles and - * rectangles to the display, and color and fill them. - * - * @class - * @extends DisplayObjectContainer - * @namespace PIXI - */ -function Graphics() -{ - DisplayObjectContainer.call(this); - - this.renderable = true; - - /** - * The alpha value used when filling the Graphics object. - * - * @member {number} - * @default 1 - */ - this.fillAlpha = 1; - - /** - * The width (thickness) of any lines drawn. - * - * @member {number} - * @default 0 - */ - this.lineWidth = 0; - - /** - * The color of any lines drawn. - * - * @member {string} - * @default 0 - */ - this.lineColor = 0; - - /** - * Graphics data - * - * @member {GraphicsData[]} - * @private - */ - this.graphicsData = []; - - /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * Current path - * - * @member {GraphicsData} - * @private - */ - this.currentPath = null; - - /** - * Array containing some WebGL-related properties used by the WebGL renderer. - * - * @member {object} - * @private - */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; - - /** - * Whether this shape is being used as a mask. - * - * @member {boolean} - */ - this.isMask = false; - - /** - * The bounds' padding used for bounds calculation. - * - * @member {number} - */ - this.boundsPadding = 0; - - /** - * A cache of the local bounds to prevent recalculation. - * - * @member {Rectangle} - * @private - */ - this._localBounds = new math.Rectangle(0,0,1,1); - - /** - * Used to detect if the graphics object has changed. If this is set to true then the graphics - * object will be recalculated. - * - * @member {boolean} - * @private - */ - this.dirty = true; - - /** - * Used to detect if the WebGL graphics object has changed. If this is set to true then the - * graphics object will be recalculated. - * - * @member {boolean} - * @private - */ - this.glDirty = false; - - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; -} - -// constructor -Graphics.prototype = Object.create(DisplayObjectContainer.prototype); -Graphics.prototype.constructor = Graphics; -module.exports = Graphics; - -Object.defineProperties(Graphics.prototype, { - /** - * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. - * This is useful if your graphics element does not change often, as it will speed up the rendering - * of the object in exchange for taking up texture memory. It is also useful if you need the graphics - * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if - * you are constantly redrawing the graphics element. - * - * @member {boolean} - * @memberof Graphics# - * @default false - * @private - */ - cacheAsBitmap: { - get: function () - { - return this._cacheAsBitmap; - }, - set: function (value) - { - this._cacheAsBitmap = value; - - if (this._cacheAsBitmap) - { - this._generateCachedSprite(); - } - else - { - this.destroyCachedSprite(); - this.dirty = true; - } - } - } -}); - -/** - * Creates a new Graphics object with the same values as this one. - * - * @return {Graphics} - */ -GraphicsData.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = this.dirty; - clone.glDirty = this.glDirty; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData.clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {Graphics} - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (arguments.length < 3) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); - } - else - { - // otherwise its empty so lets just set the line properties - this.currentPath.lineWidth = this.lineWidth; - this.currentPath.lineColor = this.lineColor; - this.currentPath.lineAlpha = this.lineAlpha; - } - } - - return this; -}; - -/** - * Moves the current drawing position to x, y. - * - * @param x {number} the X coordinate to move to - * @param y {number} the Y coordinate to move to - * @return {Graphics} - */ -Graphics.prototype.moveTo = function (x, y) -{ - this.drawShape(new math.Polygon([x,y])); - - return this; -}; - -/** - * Draws a line using the current line style from the current drawing position to (x, y); - * The current drawing position is then set to (x, y). - * - * @param x {number} the X coordinate to draw to - * @param y {number} the Y coordinate to draw to - * @return {Graphics} - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a quadratic bezier curve and then draws it. - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @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 {Graphics} - */ -Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var xa, - ya, - n = 20, - points = this.currentPath.shape.points; - - if (points.length === 0) - { - this.moveTo(0, 0); - } - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - for (var i = 1; i <= n; ++i) - { - j = i / n; - - xa = fromX + ( (cpX - fromX) * j ); - ya = fromY + ( (cpY - fromY) * j ); - - points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), - ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); - } - - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a bezier curve and then draws it. - * - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param cpX2 {number} Second Control point x - * @param cpY2 {number} Second Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {Graphics} - */ -Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var n = 20, - dt, - dt2, - dt3, - t2, - t3, - points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - - for (var i = 1; i <= n; ++i) - { - j = i / n; - - dt = (1 - j); - dt2 = dt * dt; - dt3 = dt2 * dt; - - t2 = j * j; - t3 = t2 * j; - - points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, - dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); - } - - this.dirty = true; - - return this; -}; - -/** - * The arcTo() method creates an arc/curve between two tangents on the canvas. - * - * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! - * - * @param x1 {number} The x-coordinate of the beginning of the arc - * @param y1 {number} The y-coordinate of the beginning of the arc - * @param x2 {number} The x-coordinate of the end of the arc - * @param y2 {number} The y-coordinate of the end of the arc - * @param radius {number} The radius of the arc - * @return {Graphics} - */ -Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points.push(x1, y1); - } - } - else - { - this.moveTo(x1, y1); - } - - var points = this.currentPath.shape.points, - fromX = points[points.length-2], - fromY = points[points.length-1], - a1 = fromY - y1, - b1 = fromX - x1, - a2 = y2 - y1, - b2 = x2 - x1, - mm = Math.abs(a1 * b2 - b1 * a2); - - if (mm < 1.0e-8 || radius === 0) - { - if (points[points.length-2] !== x1 || points[points.length-1] !== y1) - { - points.push(x1, y1); - } - } - else - { - var dd = a1 * a1 + b1 * b1, - cc = a2 * a2 + b2 * b2, - tt = a1 * a2 + b1 * b2, - k1 = radius * Math.sqrt(dd) / mm, - k2 = radius * Math.sqrt(cc) / mm, - j1 = k1 * tt / dd, - j2 = k2 * tt / cc, - cx = k1 * b2 + k2 * b1, - cy = k1 * a2 + k2 * a1, - px = b1 * (k2 + j1), - py = a1 * (k2 + j1), - qx = b2 * (k1 + j2), - qy = a2 * (k1 + j2), - startAngle = Math.atan2(py - cy, px - cx), - endAngle = Math.atan2(qy - cy, qx - cx); - - this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); - } - - this.dirty = true; - - return this; -}; - -/** - * The arc method creates an arc/curve (used to create circles, or parts of circles). - * - * @param cx {number} The x-coordinate of the center of the circle - * @param cy {number} The y-coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) - * @param endAngle {number} The ending angle, in radians - * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {Graphics} - */ -Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - var points; - - // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... - if (this.currentPath) - { - points = this.currentPath.shape.points; - - if (points.length === 0) - { - points.push(startX, startY); - } - else if (points[points.length-2] !== startX || points[points.length-1] !== startY) - { - points.push(startX, startY); - } - } - else - { - this.moveTo(startX, startY); - points = this.currentPath.shape.points; - } - - if (startAngle === endAngle) - { - return this; - } - - if (!anticlockwise && endAngle <= startAngle) - { - endAngle += Math.PI * 2; - } - else if (anticlockwise && startAngle <= endAngle) - { - startAngle += Math.PI * 2; - } - - var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); - var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; - - if (sweep === 0) - { - return this; - } - - var theta = sweep/(segs*2); - var theta2 = theta*2; - - var cTheta = Math.cos(theta); - var sTheta = Math.sin(theta); - - var segMinus = segs - 1; - - var remainder = ( segMinus % 1 ) / segMinus; - - for (var i = 0; i <= segMinus; ++i) - { - var real = i + remainder * i; - var angle = ((theta) + startAngle + (theta2 * real)); - - var c = Math.cos(angle); - var s = -Math.sin(angle); - - points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, - ( (cTheta * -s) + (sTheta * c) ) * radius + cy); - } - - this.dirty = true; - - return this; -}; - -/** - * Specifies a simple one-color fill that subsequent calls to other Graphics methods - * (such as lineTo() or drawCircle()) use when drawing. - * - * @param color {number} the color of the fill - * @param alpha {number} the alpha of the fill - * @return {Graphics} - */ -Graphics.prototype.beginFill = function (color, alpha) -{ - this.filling = true; - this.fillColor = color || 0; - this.fillAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length <= 2) - { - this.currentPath.fill = this.filling; - this.currentPath.fillColor = this.fillColor; - this.currentPath.fillAlpha = this.fillAlpha; - } - } - return this; -}; - -/** - * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. - * - * @return {Graphics} - */ -Graphics.prototype.endFill = function () -{ - this.filling = false; - this.fillColor = null; - this.fillAlpha = 1; - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @return {Graphics} - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {Graphics} - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {Graphics} - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {Array} The path data used to construct the polygon. - * @return {Graphics} - */ -Graphics.prototype.drawPolygon = function (path) -{ - if (!(path instanceof Array)) - { - path = Array.prototype.slice.call(arguments); - } - - this.drawShape(new math.Polygon(path)); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {Graphics} - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty = true; - this.clearDirty = true; - this.graphicsData = []; - - return this; -}; - -/** - * Useful function that returns a texture of the graphics object that can then be used to create sprites - * This can be quite useful if your geometry is complicated and needs to be reused multiple times. - * - * @param resolution {number} The resolution of the texture being generated - * @param scaleMode {number} Should be one of the scaleMode consts - * @return {Texture} a texture of the graphics object - */ -Graphics.prototype.generateTexture = function (resolution, scaleMode) -{ - resolution = resolution || 1; - - var bounds = this.getBounds(); - - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); - - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - canvasBuffer.context.scale(resolution, resolution); - - canvasBuffer.context.translate(-bounds.x,-bounds.y); - - CanvasGraphics.renderGraphics(this, canvasBuffer.context); - - return texture; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (this.isMask === true) - { - return; - } - - // this code may still be needed so leaving for now.. - // - /* - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture on the gpu too! - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.worldAlpha = this.worldAlpha; - - Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); - - return; - } - - */ - - if (this.glDirty) - { - this.dirty = true; - this.glDirty = false; - } - - renderer.setObjectRenderer(renderer.objectRenderers.graphics); - renderer.objectRenderers.graphics.render(this); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} - * @private - */ -Graphics.prototype.renderCanvas = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.alpha <= 0 || this.isMask === true) - { - return; - } - - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.alpha = this.alpha; - - Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); - - return; - } - else - { - var context = renderer.context; - var transform = this.worldTransform; - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - var resolution = renderer.resolution; - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } - } -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @return {Rectangle} the rectangular bounding area - */ -Graphics.prototype.getBounds = function (matrix) -{ - // return an empty object if the item is a mask! - if (this.isMask) - { - return math.Rectangle.EMPTY; - } - - if (this.dirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.dirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - return this._bounds; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; - - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; -}; - -/** - * Generates the cached sprite when the sprite has cacheAsBitmap = true - * - * @private - */ -Graphics.prototype._generateCachedSprite = function () -{ - var bounds = this.getLocalBounds(); - - if (!this._cachedSprite) - { - var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); - var texture = Texture.fromCanvas(canvasBuffer.canvas); - - this._cachedSprite = new Sprite(texture); - this._cachedSprite.buffer = canvasBuffer; - - this._cachedSprite.worldTransform = this.worldTransform; - } - else - { - this._cachedSprite.buffer.resize(bounds.width, bounds.height); - } - - // leverage the anchor to account for the offset of the element - this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); - this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); - - // this._cachedSprite.buffer.context.save(); - this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); - - // make sure we set the alpha of the graphics to 1 for the render.. - this.worldAlpha = 1; - - // now render the graphic.. - CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); - - this._cachedSprite.alpha = this.alpha; -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Graphics.prototype.updateCachedSpriteTexture = function () -{ - var cachedSprite = this._cachedSprite; - var texture = cachedSprite.texture; - var canvas = cachedSprite.buffer.canvas; - - texture.baseTexture.width = canvas.width; - texture.baseTexture.height = canvas.height; - texture.crop.width = texture.frame.width = canvas.width; - texture.crop.height = texture.frame.height = canvas.height; - - cachedSprite._width = canvas.width; - cachedSprite._height = canvas.height; - - // update the dirty base textures - texture.baseTexture.dirty(); -}; - -/** - * Destroys a previous cached sprite. - * - */ -Graphics.prototype.destroyCachedSprite = function () -{ - this._cachedSprite.texture.destroy(true); - - // let the gc collect the unused sprite - // TODO could be object pooled! - this._cachedSprite = null; -}; - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. - * @return {GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = this.filling; - this.currentPath = data; - } - - this.dirty = true; - - return data; -}; diff --git a/src/core/primitives/GraphicsData.js b/src/core/primitives/GraphicsData.js deleted file mode 100644 index 788f6f2..0000000 --- a/src/core/primitives/GraphicsData.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * A GraphicsData object. - * - * @class - * @namespace PIXI - */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - this.lineWidth = lineWidth; - this.lineColor = lineColor; - this.lineAlpha = lineAlpha; - this._lineTint = lineColor; - - this.fillColor = fillColor; - this.fillAlpha = fillAlpha; - this._fillTint = fillColor; - this.fill = fill; - - this.shape = shape; - this.type = shape.type; -} - -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {GraphicsData} - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js deleted file mode 100644 index 1a508f9..0000000 --- a/src/core/primitives/GraphicsRenderer.js +++ /dev/null @@ -1,874 +0,0 @@ -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; - -/** - * 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 ); - -// var matrix = graphics.worldTransform.clone(); -// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); -// matrix.append(graphics.worldTransform); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.maskManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.maskManager.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.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); - - 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/primitives/WebGLGraphicsData.js b/src/core/primitives/WebGLGraphicsData.js deleted file mode 100755 index 41be102..0000000 --- a/src/core/primitives/WebGLGraphicsData.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @class - * @private - */ -function WebGLGraphicsData(gl) { - this.gl = gl; - - //TODO does this need to be split before uploding?? - this.color = [0,0,0]; // color split! - this.points = []; - this.indices = []; - this.buffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - this.mode = 1; - this.alpha = 1; - this.dirty = true; -} - -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; -module.exports = WebGLGraphicsData; - -/** - * - */ -WebGLGraphicsData.prototype.reset = function () { - this.points = []; - this.indices = []; -}; - -/** - * - */ -WebGLGraphicsData.prototype.upload = function () { - var gl = this.gl; - -// this.lastIndex = graphics.graphicsData.length; - this.glPoints = new Float32Array(this.points); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); - - this.glIndicies = new Uint16Array(this.indices); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); - - this.dirty = false; -}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6c1fb7d..4e82baa 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -174,6 +174,9 @@ // time to create the render managers! each one focuses on managing a state in webGL + // initialize the context so it is ready for the managers. + this._initContext(); + /** * Deals with managing the shader programs and their attribs * @member {WebGLShaderManager} @@ -204,17 +207,11 @@ */ this.blendModeManager = new WebGLBlendModeManager(this); - - this.blendModes = null; this._boundUpdateTexture = this.updateTexture.bind(this); this._boundDestroyTexture = this.destroyTexture.bind(this); - - - // time init the context.. - this._initContext(); this.currentRenderTarget = this.renderTarget; @@ -228,27 +225,18 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - /** - * Manages the renderer of specific objects. - * - * @member {object} - */ - this.objectRenderers = {}; - this.currentRenderer = new ObjectRenderer(); - - // create an instance of each registered object renderer - for (var o in WebGLRenderer._objectRenderers) { - this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); - } - + this.initPlugins(); } // constructor WebGLRenderer.prototype.constructor = WebGLRenderer; module.exports = WebGLRenderer; +WebGLRenderer.glContextId = 0; + +utils.pluginTarget.mixin(WebGLRenderer); utils.eventTarget.mixin(WebGLRenderer.prototype); Object.defineProperties(WebGLRenderer.prototype, { @@ -297,11 +285,9 @@ gl.enable(gl.BLEND); this.renderTarget = new RenderTarget(this.gl, this.width, this.height, null, true); - this.emit('context', gl); - // now resize and we are good to go! this.resize(this.width, this.height); }; @@ -349,7 +335,7 @@ gl.clear(gl.COLOR_BUFFER_BIT); } - this.renderDisplayObject(object, this.renderTarget)//this.projection); + this.renderDisplayObject(object, this.renderTarget);//this.projection); }; /** @@ -536,12 +522,7 @@ this.maskManager.destroy(); this.filterManager.destroy(); - for (var o in this.objectRenderers) { - this.objectRenderers[o].destroy(); - this.objectRenderers[o] = null; - } - - this.objectRenderers = null; + this.destroyPlugins(); // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -611,11 +592,3 @@ this.blendModes[CONST.blendModes.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; } }; - -WebGLRenderer.glContextId = 0; -WebGLRenderer._objectRenderers = {}; - -WebGLRenderer.registerObjectRenderer = function (name, ctor) { - WebGLRenderer._objectRenderers[name] = ctor; -}; - diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js index 87c99f0..d6ed3c6 100644 --- a/src/core/renderers/webgl/managers/WebGLFilterManager.js +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -51,7 +51,7 @@ */ WebGLFilterManager.prototype.begin = function (buffer) { - this.defaultShader = this.renderer.shaderManager.defaultShader; + this.defaultShader = this.renderer.shaderManager.plugins.defaultShader; this.width = this.renderer.projection.x * 2; this.height = -this.renderer.projection.y * 2; @@ -385,7 +385,7 @@ if (!shader) { - shader = new Shader(gl); + shader = new Shader(this.renderer.shaderManager); shader.fragmentSrc = filter.fragmentSrc; shader.uniforms = filter.uniforms; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js index 6a1f782..d6a11c1 100644 --- a/src/core/renderers/webgl/managers/WebGLMaskManager.js +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -126,11 +126,11 @@ // bind the graphics object.. var projection = this.renderer.projection, offset = this.renderer.offset, - shader;// = this.renderer.shaderManager.primitiveShader; + shader;// = this.renderer.shaderManager.plugins.primitiveShader; if (webGLData.mode === 1) { - shader = this.renderer.shaderManager.complexPrimitiveShader; + shader = this.renderer.shaderManager.plugins.complexPrimitiveShader; this.renderer.shaderManager.setShader(shader); @@ -156,7 +156,7 @@ else { //this.renderer.shaderManager.activatePrimitiveShader(); - shader = this.renderer.shaderManager.primitiveShader; + shader = this.renderer.shaderManager.plugins.primitiveShader; this.renderer.shaderManager.setShader( shader ); gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); @@ -292,11 +292,11 @@ */ WebGLMaskManager.prototype.pushMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); if (maskData.dirty) { - this.renderer.objectRenderers.graphics.updateGraphics(maskData, this.renderer.gl); + this.renderer.plugins.graphics.updateGraphics(maskData, this.renderer.gl); } if (!maskData._webGL[this.renderer.gl.id].data.length) @@ -314,7 +314,7 @@ */ WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); this.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); }; diff --git a/src/core/renderers/webgl/managers/WebGLShaderManager.js b/src/core/renderers/webgl/managers/WebGLShaderManager.js index 90c6477..9ebb58e 100644 --- a/src/core/renderers/webgl/managers/WebGLShaderManager.js +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -1,9 +1,5 @@ var WebGLManager = require('./WebGLManager'), - PrimitiveShader = require('../shaders/PrimitiveShader'), - ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), - Shader = require('../shaders/Shader'), - FastShader = require('../shaders/FastShader'), - StripShader = require('../shaders/StripShader'); + utils = require('../../../utils'); /** * @class @@ -51,41 +47,16 @@ */ this.currentShader = null; - // this shader is used for rendering primitives - this.primitiveShader = null; - - // this shader is used for rendering triangle strips - this.complexPrimitiveShader = null; - - // this shader is used for the default sprite rendering - this.defaultShader = null; - - // this shader is used for the fast sprite rendering - this.fastShader = null; - - // the next one is used for rendering triangle strips - this.stripShader = null; + this.initPlugins(); // listen for context and update necessary shaders var self = this; - this.renderer.on('context', function (event) + this.renderer.on('context', function () { - var gl = event.data; - - // this shader is used for rendering primitives - self.primitiveShader = new PrimitiveShader(gl); - - // this shader is used for rendering triangle strips - self.complexPrimitiveShader = new ComplexPrimitiveShader(gl); - - // this shader is used for the default sprite rendering - self.defaultShader = new Shader(gl); - - // this shader is used for the fast sprite rendering - self.fastShader = new FastShader(gl); - - // the next one is used for rendering triangle strips - self.stripShader = new StripShader(gl); + for (var o in this.plugins) + { + this.plugins[o] = new (this.plugins[o].constructor)(self); + } self.setShader(self.defaultShader); }); @@ -95,6 +66,8 @@ WebGLShaderManager.prototype.constructor = WebGLShaderManager; module.exports = WebGLShaderManager; +utils.pluginTarget.mixin(WebGLShaderManager); + /** * Takes the attributes given in parameters. * @@ -164,24 +137,11 @@ */ WebGLShaderManager.prototype.destroy = function () { + this.destroyPlugins(); + this.attribState = null; this.tempAttribState = null; - this.primitiveShader.destroy(); - this.primitiveShader = null; - - this.complexPrimitiveShader.destroy(); - this.complexPrimitiveShader = null; - - this.defaultShader.destroy(); - this.defaultShader = null; - - this.fastShader.destroy(); - this.fastShader = null; - - this.stripShader.destroy(); - this.stripShader = null; - this.renderer = null; }; diff --git a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js b/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js deleted file mode 100644 index f8a07da..0000000 --- a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js +++ /dev/null @@ -1,53 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function ComplexPrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform vec3 tint;', - 'uniform float alpha;', - 'uniform vec3 color;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - color: { type: '3f', value: [0,0,0] }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); -ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; -module.exports = ComplexPrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/FastShader.js b/src/core/renderers/webgl/shaders/FastShader.js index d198f10..c80d040 100644 --- a/src/core/renderers/webgl/shaders/FastShader.js +++ b/src/core/renderers/webgl/shaders/FastShader.js @@ -1,15 +1,16 @@ -var Shader = require('./Shader'); +var Shader = require('./Shader'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @extends Shader * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. */ -function FastShader(gl) +function FastShader(shaderManager) { Shader.call(this, - gl, + shaderManager, // vertex shader [ 'attribute vec2 aVertexPosition;', @@ -70,3 +71,5 @@ FastShader.prototype = Object.create(Shader.prototype); FastShader.prototype.constructor = FastShader; module.exports = FastShader; + +WebGLShaderManager.registerPlugin('fastShader', FastShader); diff --git a/src/core/renderers/webgl/shaders/PrimitiveShader.js b/src/core/renderers/webgl/shaders/PrimitiveShader.js deleted file mode 100644 index 753c475..0000000 --- a/src/core/renderers/webgl/shaders/PrimitiveShader.js +++ /dev/null @@ -1,55 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform float alpha;', - 'uniform float flipY;', - 'uniform vec3 tint;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aColor:0 - } - ); -} - -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; -module.exports = PrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/Shader.js b/src/core/renderers/webgl/shaders/Shader.js index d065a76..c60c5f9 100644 --- a/src/core/renderers/webgl/shaders/Shader.js +++ b/src/core/renderers/webgl/shaders/Shader.js @@ -1,12 +1,16 @@ -var utils = require('../../../utils'); +var utils = require('../../../utils'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @namespace PIXI - * @param [fragmentSrc] {string} The source of the fragment shader. + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. * @param [vertexSrc] {string} The source of the vertex shader. + * @param [fragmentSrc] {string} The source of the fragment shader. + * @param customUniforms {object} Custom uniforms to use to augment the built-in ones. + * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Shader(gl, vertexSrc, fragmentSrc, customUniforms, customAttributes) +function Shader(shaderManager, vertexSrc, fragmentSrc, customUniforms, customAttributes) { /** * @member {number} @@ -18,7 +22,7 @@ * @member {WebGLContext} * @readonly */ - this.gl = gl; + this.gl = shaderManager.renderer.gl; /** * The WebGL program. @@ -31,8 +35,8 @@ uSampler: { type: 'sampler2D', value: 0 }, projectionVector: { type: '2f', value: { x: 0, y: 0 } }, offsetVector: { type: '2f', value: { x: 0, y: 0 } }, - projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, - 0, 1, 0, + projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, + 0, 1, 0, 0, 0, 1) }, dimensions: { type: '4f', value: new Float32Array(4) } }; @@ -103,6 +107,8 @@ Shader.prototype.constructor = Shader; module.exports = Shader; +WebGLShaderManager.registerPlugin('defaultShader', Shader); + Shader.prototype.init = function () { this.compile(); @@ -506,4 +512,4 @@ } return shader; -}; \ No newline at end of file +}; diff --git a/src/core/renderers/webgl/shaders/SpriteShader.js b/src/core/renderers/webgl/shaders/SpriteShader.js deleted file mode 100644 index 1805b0a..0000000 --- a/src/core/renderers/webgl/shaders/SpriteShader.js +++ /dev/null @@ -1,42 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function SpriteShader(gl) -{ - Shader.call(this, - gl, - null, - // fragment shader - [ - 'precision lowp float;', - - 'varying vec2 vTextureCoord;', - 'varying vec4 vColor;', - - 'uniform sampler2D uSampler;', - - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aTextureCoord: 0, - aColor: 0 - } - ); -} - -SpriteShader.prototype = Object.create(Shader.prototype); -SpriteShader.prototype.constructor = SpriteShader; -module.exports = SpriteShader; diff --git a/src/core/renderers/webgl/shaders/StripShader.js b/src/core/renderers/webgl/shaders/StripShader.js deleted file mode 100644 index f487381..0000000 --- a/src/core/renderers/webgl/shaders/StripShader.js +++ /dev/null @@ -1,54 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function StripShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - // 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform vec2 projectionVector;', - 'uniform vec2 offsetVector;', - - 'varying vec2 vTextureCoord;', - - 'void main(void){', - ' vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);', - ' v -= offsetVector.xyx;', - ' gl_Position = vec4( v.x / projectionVector.x -1.0, v.y / -projectionVector.y + 1.0 , 0.0, 1.0);', - ' vTextureCoord = aTextureCoord;', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'uniform float alpha;', - 'uniform sampler2D uSampler;', - - 'varying vec2 vTextureCoord;', - - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y)) * alpha;', - '}' - ].join('\n'), - // custom uniforms - { - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -StripShader.prototype = Object.create(Shader.prototype); -StripShader.prototype.constructor = StripShader; -module.exports = StripShader; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index 0a34e55..43e2d00 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -33,7 +33,7 @@ * @property texture * @type Any */ - this.texture = null + this.texture = null; this.width = 0; this.height = 0; @@ -56,11 +56,11 @@ this.frameBuffer = gl.createFramebuffer(); - /* + /* A frame buffer needs a target to render to.. create a texture and bind it attach it to the framebuffer.. */ - + this.texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, this.texture); @@ -70,13 +70,13 @@ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); 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); - + gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer ); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0); - - /* + + /* The stencil buffer is used for masking in pixi lets create one and then add attach it to the framebuffer.. */ @@ -86,7 +86,7 @@ } this.resize(width, height); - + }; RenderTarget.prototype.constructor = RenderTarget; @@ -94,21 +94,21 @@ /** * Clears the filter texture. -* +* * @method clear */ RenderTarget.prototype.clear = function() { var gl = this.gl; - + gl.clearColor(0,0,0, 0); gl.clear(gl.COLOR_BUFFER_BIT); }; RenderTarget.prototype.activate = function() { - this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer ); -} + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer); +}; /** * Resizes the texture to the specified width and height @@ -119,25 +119,25 @@ */ RenderTarget.prototype.resize = function(width, height) { - width = width | 0; - height = height | 0; - - if(this.width === width && this.height === height) return; + if (this.width === width && this.height === height) { + return; + } this.width = width; this.height = height; + this.projectionMatrix = new math.Matrix(); - - if(!this.root) + + if (!this.root) { var gl = this.gl; this.projectionMatrix.a = 1/width*2; - this.projectionMatrix.d = 1/height*2; + this.projectionMatrix.d = -1/height*2; this.projectionMatrix.tx = -1; - this.projectionMatrix.ty = -1; + this.projectionMatrix.ty = 1; gl.bindTexture(gl.TEXTURE_2D, this.texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width , height , 0, gl.RGBA, gl.UNSIGNED_BYTE, null); @@ -157,7 +157,7 @@ /** * Destroys the filter texture. -* +* * @method destroy */ RenderTarget.prototype.destroy = function() diff --git a/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js b/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js index a63f4d7..3f37b7d 100644 --- a/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js +++ b/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js @@ -183,7 +183,7 @@ */ WebGLFastSpriteBatch.prototype.begin = function (spriteBatch) { - this.shader = this.renderer.shaderManager.fastShader; + this.shader = this.renderer.shaderManager.plugins.fastShader; this.matrix = spriteBatch.worldTransform.toArray(true); diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index bf8db28..2dc47ff 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -201,8 +201,8 @@ Sprite.prototype._renderWebGL = function (renderer) { - renderer.setObjectRenderer(renderer.objectRenderers.sprite); - renderer.objectRenderers.sprite.render(this); + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); }; /** diff --git a/src/core/sprites/SpriteBatch.js b/src/core/sprites/SpriteBatch.js index 63cebc0..4fe33e4 100644 --- a/src/core/sprites/SpriteBatch.js +++ b/src/core/sprites/SpriteBatch.js @@ -61,7 +61,7 @@ renderer.spriteBatch.stop(); - renderer.shaderManager.setShader(renderer.shaderManager.fastShader); + renderer.shaderManager.setShader(renderer.shaderManager.plugins.fastShader); renderer.fastSpriteBatch.begin(this); renderer.fastSpriteBatch.render(this); diff --git a/src/core/sprites/SpriteRenderer.js b/src/core/sprites/SpriteRenderer.js deleted file mode 100644 index fd9a802..0000000 --- a/src/core/sprites/SpriteRenderer.js +++ /dev/null @@ -1,537 +0,0 @@ -var ObjectRenderer = require('../renderers/webgl/utils/ObjectRenderer'), - Shader = require('../renderers/webgl/shaders/Shader'), - SpriteShader = require('../renderers/webgl/shaders/SpriteShader'), - WebGLRenderer = require('../renderers/webgl/WebGLRenderer'); - math = require('../math'); - -/** - * @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.setupContext(); -} - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; -module.exports = 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 = sprite; - 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 - gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, this.renderer.currentRenderTarget.projectionMatrix.toArray(true)); - } - } - - 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; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js new file mode 100644 index 0000000..c006ba4 --- /dev/null +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -0,0 +1,533 @@ +var ObjectRenderer = require('../../renderers/webgl/utils/ObjectRenderer'), + Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLRenderer = require('../../renderers/webgl/WebGLRenderer'), + SpriteShader = require('./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) +{ + 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; + + this.setupContext(); +} + +SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); +SpriteRenderer.prototype.constructor = SpriteRenderer; +module.exports = SpriteRenderer; + +WebGLRenderer.registerPlugin('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(this.renderer.shaderManager); + + // 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(this.renderer.shaderManager, 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 + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, this.renderer.currentRenderTarget.projectionMatrix.toArray(true)); + } + } + + 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; +}; diff --git a/src/core/sprites/webgl/SpriteShader.js b/src/core/sprites/webgl/SpriteShader.js new file mode 100644 index 0000000..3925e9f --- /dev/null +++ b/src/core/sprites/webgl/SpriteShader.js @@ -0,0 +1,45 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function SpriteShader(shaderManager) +{ + Shader.call(this, + shaderManager, + null, + // fragment shader + [ + 'precision lowp float;', + + 'varying vec2 vTextureCoord;', + 'varying vec4 vColor;', + + 'uniform sampler2D uSampler;', + + 'void main(void){', + ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aTextureCoord: 0, + aColor: 0 + } + ); +} + +SpriteShader.prototype = Object.create(Shader.prototype); +SpriteShader.prototype.constructor = SpriteShader; +module.exports = SpriteShader; + +WebGLShaderManager.registerPlugin('fastShader', SpriteShader); diff --git a/src/core/utils/index.js b/src/core/utils/index.js index bfae7ae..1d451c0 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -7,9 +7,10 @@ _uid: 0, _saidHello: false, - PolyK: require('./PolyK'), - EventData: require('./EventData'), - eventTarget: require('./eventTarget'), + PolyK: require('./PolyK'), + EventData: require('./EventData'), + eventTarget: require('./eventTarget'), + pluginTarget: require('./pluginTarget'), /** * Gets the next uuid diff --git a/src/core/utils/pluginTarget.js b/src/core/utils/pluginTarget.js new file mode 100644 index 0000000..885fe0e --- /dev/null +++ b/src/core/utils/pluginTarget.js @@ -0,0 +1,37 @@ +/** + * Mixins functionality to make an object have "plugins". + * + * @mixin + * @namespace PIXI + * @param obj {object} The object to mix into. + * @example + * function MyObject() {} + * + * pluginTarget.mixin(MyObject); + */ +function pluginTarget(obj) { + obj.__plugins = {}; + + obj.registerPlugin = function (pluginName, ctor) { + obj.__plugins[pluginName] = ctor; + }; + + obj.prototype.initPlugins = function () { + this.plugins = {}; + + for (var o in obj.__plugins) { + this.plugins[o] = new (obj.__plugins[o])(this); + } + }; + + obj.prototype.destroyPlugins = function () { + for (var o in this.plugins) { + this.plugins[o].destroy(); + this.plugins[o] = null; + } + + this.plugins = null; + }; +} + +module.exports = pluginTarget; diff --git a/src/extras/Strip.js b/src/extras/Strip.js index 57691b5..6bf78cb 100644 --- a/src/extras/Strip.js +++ b/src/extras/Strip.js @@ -89,7 +89,7 @@ this._initWebGL(renderer); } - renderer.shaderManager.setShader(renderer.shaderManager.stripShader); + renderer.shaderManager.setShader(renderer.shaderManager.plugins.stripShader); this._renderStrip(renderer); @@ -128,7 +128,7 @@ var gl = renderer.gl; var projection = renderer.projection, offset = renderer.offset, - shader = renderer.shaderManager.stripShader; + shader = renderer.shaderManager.plugins.stripShader; var drawMode = this.drawMode === Strip.DrawModes.TRIANGLE_STRIP ? gl.TRIANGLE_STRIP : gl.TRIANGLES; diff --git a/src/extras/StripShader.js b/src/extras/StripShader.js new file mode 100644 index 0000000..3e7c47f --- /dev/null +++ b/src/extras/StripShader.js @@ -0,0 +1,56 @@ +var core = require('../core'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function StripShader(shaderManager) +{ + core.Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + // 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform vec2 projectionVector;', + 'uniform vec2 offsetVector;', + + 'varying vec2 vTextureCoord;', + + 'void main(void){', + ' vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);', + ' v -= offsetVector.xyx;', + ' gl_Position = vec4( v.x / projectionVector.x -1.0, v.y / -projectionVector.y + 1.0 , 0.0, 1.0);', + ' vTextureCoord = aTextureCoord;', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'uniform float alpha;', + 'uniform sampler2D uSampler;', + + 'varying vec2 vTextureCoord;', + + 'void main(void){', + ' gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y)) * alpha;', + '}' + ].join('\n'), + // custom uniforms + { + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +StripShader.prototype = Object.create(core.Shader.prototype); +StripShader.prototype.constructor = StripShader; +module.exports = StripShader; + +core.WebGLShaderManager.registerPlugin('stripShader', StripShader); diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js new file mode 100644 index 0000000..41be102 --- /dev/null +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -0,0 +1,48 @@ +/** + * @class + * @private + */ +function WebGLGraphicsData(gl) { + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +} + +WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; +module.exports = WebGLGraphicsData; + +/** + * + */ +WebGLGraphicsData.prototype.reset = function () { + this.points = []; + this.indices = []; +}; + +/** + * + */ +WebGLGraphicsData.prototype.upload = function () { + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; diff --git a/src/core/index.js b/src/core/index.js index 6f744e9..1d0a398 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -23,12 +23,12 @@ Sprite: require('./sprites/Sprite'), SpriteBatch: require('./sprites/SpriteBatch'), - SpriteRenderer: require('./sprites/SpriteRenderer'), + SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), // primitives - Graphics: require('./primitives/Graphics'), - GraphicsData: require('./primitives/GraphicsData'), - GraphicsRenderer: require('./primitives/GraphicsRenderer'), + Graphics: require('./graphics/Graphics'), + GraphicsData: require('./graphics/GraphicsData'), + GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,10 +42,8 @@ CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), // renderers - webgl - WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), - // WebGLGraphics: require('./renderers/webgl/utils/WebGLGraphics'), - - + WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), + Shader: require('./renderers/webgl/shaders/Shader'), /** * This helper function will automatically detect which renderer you should be using. @@ -103,7 +101,3 @@ return core.autoDetectRenderer(width, height, options, isAndroid); } }; - -// Adding here for now.. sure there is a better place! -core.WebGLRenderer.registerObjectRenderer('sprite', core.SpriteRenderer); -core.WebGLRenderer.registerObjectRenderer('graphics', core.GraphicsRenderer); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index a086950..81de05c 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -268,6 +268,6 @@ matrix.ty = this.ty; return matrix; -} +}; Matrix.IDENTITY = new Matrix(); diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js deleted file mode 100644 index 8db9b45..0000000 --- a/src/core/primitives/Graphics.js +++ /dev/null @@ -1,1116 +0,0 @@ -var DisplayObjectContainer = require('../display/DisplayObjectContainer'), - //Sprite = require('../display/Sprite'), - Texture = require('../textures/Texture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), - // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), - GraphicsData = require('./GraphicsData'), - math = require('../math'), - CONST = require('../const'); - -/** - * The Graphics class contains methods used to draw primitive shapes such as lines, circles and - * rectangles to the display, and color and fill them. - * - * @class - * @extends DisplayObjectContainer - * @namespace PIXI - */ -function Graphics() -{ - DisplayObjectContainer.call(this); - - this.renderable = true; - - /** - * The alpha value used when filling the Graphics object. - * - * @member {number} - * @default 1 - */ - this.fillAlpha = 1; - - /** - * The width (thickness) of any lines drawn. - * - * @member {number} - * @default 0 - */ - this.lineWidth = 0; - - /** - * The color of any lines drawn. - * - * @member {string} - * @default 0 - */ - this.lineColor = 0; - - /** - * Graphics data - * - * @member {GraphicsData[]} - * @private - */ - this.graphicsData = []; - - /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * Current path - * - * @member {GraphicsData} - * @private - */ - this.currentPath = null; - - /** - * Array containing some WebGL-related properties used by the WebGL renderer. - * - * @member {object} - * @private - */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; - - /** - * Whether this shape is being used as a mask. - * - * @member {boolean} - */ - this.isMask = false; - - /** - * The bounds' padding used for bounds calculation. - * - * @member {number} - */ - this.boundsPadding = 0; - - /** - * A cache of the local bounds to prevent recalculation. - * - * @member {Rectangle} - * @private - */ - this._localBounds = new math.Rectangle(0,0,1,1); - - /** - * Used to detect if the graphics object has changed. If this is set to true then the graphics - * object will be recalculated. - * - * @member {boolean} - * @private - */ - this.dirty = true; - - /** - * Used to detect if the WebGL graphics object has changed. If this is set to true then the - * graphics object will be recalculated. - * - * @member {boolean} - * @private - */ - this.glDirty = false; - - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; -} - -// constructor -Graphics.prototype = Object.create(DisplayObjectContainer.prototype); -Graphics.prototype.constructor = Graphics; -module.exports = Graphics; - -Object.defineProperties(Graphics.prototype, { - /** - * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. - * This is useful if your graphics element does not change often, as it will speed up the rendering - * of the object in exchange for taking up texture memory. It is also useful if you need the graphics - * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if - * you are constantly redrawing the graphics element. - * - * @member {boolean} - * @memberof Graphics# - * @default false - * @private - */ - cacheAsBitmap: { - get: function () - { - return this._cacheAsBitmap; - }, - set: function (value) - { - this._cacheAsBitmap = value; - - if (this._cacheAsBitmap) - { - this._generateCachedSprite(); - } - else - { - this.destroyCachedSprite(); - this.dirty = true; - } - } - } -}); - -/** - * Creates a new Graphics object with the same values as this one. - * - * @return {Graphics} - */ -GraphicsData.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = this.dirty; - clone.glDirty = this.glDirty; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData.clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {Graphics} - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (arguments.length < 3) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); - } - else - { - // otherwise its empty so lets just set the line properties - this.currentPath.lineWidth = this.lineWidth; - this.currentPath.lineColor = this.lineColor; - this.currentPath.lineAlpha = this.lineAlpha; - } - } - - return this; -}; - -/** - * Moves the current drawing position to x, y. - * - * @param x {number} the X coordinate to move to - * @param y {number} the Y coordinate to move to - * @return {Graphics} - */ -Graphics.prototype.moveTo = function (x, y) -{ - this.drawShape(new math.Polygon([x,y])); - - return this; -}; - -/** - * Draws a line using the current line style from the current drawing position to (x, y); - * The current drawing position is then set to (x, y). - * - * @param x {number} the X coordinate to draw to - * @param y {number} the Y coordinate to draw to - * @return {Graphics} - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a quadratic bezier curve and then draws it. - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @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 {Graphics} - */ -Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var xa, - ya, - n = 20, - points = this.currentPath.shape.points; - - if (points.length === 0) - { - this.moveTo(0, 0); - } - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - for (var i = 1; i <= n; ++i) - { - j = i / n; - - xa = fromX + ( (cpX - fromX) * j ); - ya = fromY + ( (cpY - fromY) * j ); - - points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), - ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); - } - - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a bezier curve and then draws it. - * - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param cpX2 {number} Second Control point x - * @param cpY2 {number} Second Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {Graphics} - */ -Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var n = 20, - dt, - dt2, - dt3, - t2, - t3, - points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - - for (var i = 1; i <= n; ++i) - { - j = i / n; - - dt = (1 - j); - dt2 = dt * dt; - dt3 = dt2 * dt; - - t2 = j * j; - t3 = t2 * j; - - points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, - dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); - } - - this.dirty = true; - - return this; -}; - -/** - * The arcTo() method creates an arc/curve between two tangents on the canvas. - * - * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! - * - * @param x1 {number} The x-coordinate of the beginning of the arc - * @param y1 {number} The y-coordinate of the beginning of the arc - * @param x2 {number} The x-coordinate of the end of the arc - * @param y2 {number} The y-coordinate of the end of the arc - * @param radius {number} The radius of the arc - * @return {Graphics} - */ -Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points.push(x1, y1); - } - } - else - { - this.moveTo(x1, y1); - } - - var points = this.currentPath.shape.points, - fromX = points[points.length-2], - fromY = points[points.length-1], - a1 = fromY - y1, - b1 = fromX - x1, - a2 = y2 - y1, - b2 = x2 - x1, - mm = Math.abs(a1 * b2 - b1 * a2); - - if (mm < 1.0e-8 || radius === 0) - { - if (points[points.length-2] !== x1 || points[points.length-1] !== y1) - { - points.push(x1, y1); - } - } - else - { - var dd = a1 * a1 + b1 * b1, - cc = a2 * a2 + b2 * b2, - tt = a1 * a2 + b1 * b2, - k1 = radius * Math.sqrt(dd) / mm, - k2 = radius * Math.sqrt(cc) / mm, - j1 = k1 * tt / dd, - j2 = k2 * tt / cc, - cx = k1 * b2 + k2 * b1, - cy = k1 * a2 + k2 * a1, - px = b1 * (k2 + j1), - py = a1 * (k2 + j1), - qx = b2 * (k1 + j2), - qy = a2 * (k1 + j2), - startAngle = Math.atan2(py - cy, px - cx), - endAngle = Math.atan2(qy - cy, qx - cx); - - this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); - } - - this.dirty = true; - - return this; -}; - -/** - * The arc method creates an arc/curve (used to create circles, or parts of circles). - * - * @param cx {number} The x-coordinate of the center of the circle - * @param cy {number} The y-coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) - * @param endAngle {number} The ending angle, in radians - * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {Graphics} - */ -Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - var points; - - // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... - if (this.currentPath) - { - points = this.currentPath.shape.points; - - if (points.length === 0) - { - points.push(startX, startY); - } - else if (points[points.length-2] !== startX || points[points.length-1] !== startY) - { - points.push(startX, startY); - } - } - else - { - this.moveTo(startX, startY); - points = this.currentPath.shape.points; - } - - if (startAngle === endAngle) - { - return this; - } - - if (!anticlockwise && endAngle <= startAngle) - { - endAngle += Math.PI * 2; - } - else if (anticlockwise && startAngle <= endAngle) - { - startAngle += Math.PI * 2; - } - - var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); - var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; - - if (sweep === 0) - { - return this; - } - - var theta = sweep/(segs*2); - var theta2 = theta*2; - - var cTheta = Math.cos(theta); - var sTheta = Math.sin(theta); - - var segMinus = segs - 1; - - var remainder = ( segMinus % 1 ) / segMinus; - - for (var i = 0; i <= segMinus; ++i) - { - var real = i + remainder * i; - var angle = ((theta) + startAngle + (theta2 * real)); - - var c = Math.cos(angle); - var s = -Math.sin(angle); - - points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, - ( (cTheta * -s) + (sTheta * c) ) * radius + cy); - } - - this.dirty = true; - - return this; -}; - -/** - * Specifies a simple one-color fill that subsequent calls to other Graphics methods - * (such as lineTo() or drawCircle()) use when drawing. - * - * @param color {number} the color of the fill - * @param alpha {number} the alpha of the fill - * @return {Graphics} - */ -Graphics.prototype.beginFill = function (color, alpha) -{ - this.filling = true; - this.fillColor = color || 0; - this.fillAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length <= 2) - { - this.currentPath.fill = this.filling; - this.currentPath.fillColor = this.fillColor; - this.currentPath.fillAlpha = this.fillAlpha; - } - } - return this; -}; - -/** - * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. - * - * @return {Graphics} - */ -Graphics.prototype.endFill = function () -{ - this.filling = false; - this.fillColor = null; - this.fillAlpha = 1; - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @return {Graphics} - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {Graphics} - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {Graphics} - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {Array} The path data used to construct the polygon. - * @return {Graphics} - */ -Graphics.prototype.drawPolygon = function (path) -{ - if (!(path instanceof Array)) - { - path = Array.prototype.slice.call(arguments); - } - - this.drawShape(new math.Polygon(path)); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {Graphics} - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty = true; - this.clearDirty = true; - this.graphicsData = []; - - return this; -}; - -/** - * Useful function that returns a texture of the graphics object that can then be used to create sprites - * This can be quite useful if your geometry is complicated and needs to be reused multiple times. - * - * @param resolution {number} The resolution of the texture being generated - * @param scaleMode {number} Should be one of the scaleMode consts - * @return {Texture} a texture of the graphics object - */ -Graphics.prototype.generateTexture = function (resolution, scaleMode) -{ - resolution = resolution || 1; - - var bounds = this.getBounds(); - - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); - - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - canvasBuffer.context.scale(resolution, resolution); - - canvasBuffer.context.translate(-bounds.x,-bounds.y); - - CanvasGraphics.renderGraphics(this, canvasBuffer.context); - - return texture; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (this.isMask === true) - { - return; - } - - // this code may still be needed so leaving for now.. - // - /* - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture on the gpu too! - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.worldAlpha = this.worldAlpha; - - Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); - - return; - } - - */ - - if (this.glDirty) - { - this.dirty = true; - this.glDirty = false; - } - - renderer.setObjectRenderer(renderer.objectRenderers.graphics); - renderer.objectRenderers.graphics.render(this); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} - * @private - */ -Graphics.prototype.renderCanvas = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.alpha <= 0 || this.isMask === true) - { - return; - } - - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.alpha = this.alpha; - - Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); - - return; - } - else - { - var context = renderer.context; - var transform = this.worldTransform; - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - var resolution = renderer.resolution; - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } - } -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @return {Rectangle} the rectangular bounding area - */ -Graphics.prototype.getBounds = function (matrix) -{ - // return an empty object if the item is a mask! - if (this.isMask) - { - return math.Rectangle.EMPTY; - } - - if (this.dirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.dirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - return this._bounds; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; - - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; -}; - -/** - * Generates the cached sprite when the sprite has cacheAsBitmap = true - * - * @private - */ -Graphics.prototype._generateCachedSprite = function () -{ - var bounds = this.getLocalBounds(); - - if (!this._cachedSprite) - { - var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); - var texture = Texture.fromCanvas(canvasBuffer.canvas); - - this._cachedSprite = new Sprite(texture); - this._cachedSprite.buffer = canvasBuffer; - - this._cachedSprite.worldTransform = this.worldTransform; - } - else - { - this._cachedSprite.buffer.resize(bounds.width, bounds.height); - } - - // leverage the anchor to account for the offset of the element - this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); - this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); - - // this._cachedSprite.buffer.context.save(); - this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); - - // make sure we set the alpha of the graphics to 1 for the render.. - this.worldAlpha = 1; - - // now render the graphic.. - CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); - - this._cachedSprite.alpha = this.alpha; -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Graphics.prototype.updateCachedSpriteTexture = function () -{ - var cachedSprite = this._cachedSprite; - var texture = cachedSprite.texture; - var canvas = cachedSprite.buffer.canvas; - - texture.baseTexture.width = canvas.width; - texture.baseTexture.height = canvas.height; - texture.crop.width = texture.frame.width = canvas.width; - texture.crop.height = texture.frame.height = canvas.height; - - cachedSprite._width = canvas.width; - cachedSprite._height = canvas.height; - - // update the dirty base textures - texture.baseTexture.dirty(); -}; - -/** - * Destroys a previous cached sprite. - * - */ -Graphics.prototype.destroyCachedSprite = function () -{ - this._cachedSprite.texture.destroy(true); - - // let the gc collect the unused sprite - // TODO could be object pooled! - this._cachedSprite = null; -}; - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. - * @return {GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = this.filling; - this.currentPath = data; - } - - this.dirty = true; - - return data; -}; diff --git a/src/core/primitives/GraphicsData.js b/src/core/primitives/GraphicsData.js deleted file mode 100644 index 788f6f2..0000000 --- a/src/core/primitives/GraphicsData.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * A GraphicsData object. - * - * @class - * @namespace PIXI - */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - this.lineWidth = lineWidth; - this.lineColor = lineColor; - this.lineAlpha = lineAlpha; - this._lineTint = lineColor; - - this.fillColor = fillColor; - this.fillAlpha = fillAlpha; - this._fillTint = fillColor; - this.fill = fill; - - this.shape = shape; - this.type = shape.type; -} - -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {GraphicsData} - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js deleted file mode 100644 index 1a508f9..0000000 --- a/src/core/primitives/GraphicsRenderer.js +++ /dev/null @@ -1,874 +0,0 @@ -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; - -/** - * 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 ); - -// var matrix = graphics.worldTransform.clone(); -// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); -// matrix.append(graphics.worldTransform); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.maskManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.maskManager.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.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); - - 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/primitives/WebGLGraphicsData.js b/src/core/primitives/WebGLGraphicsData.js deleted file mode 100755 index 41be102..0000000 --- a/src/core/primitives/WebGLGraphicsData.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @class - * @private - */ -function WebGLGraphicsData(gl) { - this.gl = gl; - - //TODO does this need to be split before uploding?? - this.color = [0,0,0]; // color split! - this.points = []; - this.indices = []; - this.buffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - this.mode = 1; - this.alpha = 1; - this.dirty = true; -} - -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; -module.exports = WebGLGraphicsData; - -/** - * - */ -WebGLGraphicsData.prototype.reset = function () { - this.points = []; - this.indices = []; -}; - -/** - * - */ -WebGLGraphicsData.prototype.upload = function () { - var gl = this.gl; - -// this.lastIndex = graphics.graphicsData.length; - this.glPoints = new Float32Array(this.points); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); - - this.glIndicies = new Uint16Array(this.indices); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); - - this.dirty = false; -}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6c1fb7d..4e82baa 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -174,6 +174,9 @@ // time to create the render managers! each one focuses on managing a state in webGL + // initialize the context so it is ready for the managers. + this._initContext(); + /** * Deals with managing the shader programs and their attribs * @member {WebGLShaderManager} @@ -204,17 +207,11 @@ */ this.blendModeManager = new WebGLBlendModeManager(this); - - this.blendModes = null; this._boundUpdateTexture = this.updateTexture.bind(this); this._boundDestroyTexture = this.destroyTexture.bind(this); - - - // time init the context.. - this._initContext(); this.currentRenderTarget = this.renderTarget; @@ -228,27 +225,18 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - /** - * Manages the renderer of specific objects. - * - * @member {object} - */ - this.objectRenderers = {}; - this.currentRenderer = new ObjectRenderer(); - - // create an instance of each registered object renderer - for (var o in WebGLRenderer._objectRenderers) { - this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); - } - + this.initPlugins(); } // constructor WebGLRenderer.prototype.constructor = WebGLRenderer; module.exports = WebGLRenderer; +WebGLRenderer.glContextId = 0; + +utils.pluginTarget.mixin(WebGLRenderer); utils.eventTarget.mixin(WebGLRenderer.prototype); Object.defineProperties(WebGLRenderer.prototype, { @@ -297,11 +285,9 @@ gl.enable(gl.BLEND); this.renderTarget = new RenderTarget(this.gl, this.width, this.height, null, true); - this.emit('context', gl); - // now resize and we are good to go! this.resize(this.width, this.height); }; @@ -349,7 +335,7 @@ gl.clear(gl.COLOR_BUFFER_BIT); } - this.renderDisplayObject(object, this.renderTarget)//this.projection); + this.renderDisplayObject(object, this.renderTarget);//this.projection); }; /** @@ -536,12 +522,7 @@ this.maskManager.destroy(); this.filterManager.destroy(); - for (var o in this.objectRenderers) { - this.objectRenderers[o].destroy(); - this.objectRenderers[o] = null; - } - - this.objectRenderers = null; + this.destroyPlugins(); // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -611,11 +592,3 @@ this.blendModes[CONST.blendModes.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; } }; - -WebGLRenderer.glContextId = 0; -WebGLRenderer._objectRenderers = {}; - -WebGLRenderer.registerObjectRenderer = function (name, ctor) { - WebGLRenderer._objectRenderers[name] = ctor; -}; - diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js index 87c99f0..d6ed3c6 100644 --- a/src/core/renderers/webgl/managers/WebGLFilterManager.js +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -51,7 +51,7 @@ */ WebGLFilterManager.prototype.begin = function (buffer) { - this.defaultShader = this.renderer.shaderManager.defaultShader; + this.defaultShader = this.renderer.shaderManager.plugins.defaultShader; this.width = this.renderer.projection.x * 2; this.height = -this.renderer.projection.y * 2; @@ -385,7 +385,7 @@ if (!shader) { - shader = new Shader(gl); + shader = new Shader(this.renderer.shaderManager); shader.fragmentSrc = filter.fragmentSrc; shader.uniforms = filter.uniforms; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js index 6a1f782..d6a11c1 100644 --- a/src/core/renderers/webgl/managers/WebGLMaskManager.js +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -126,11 +126,11 @@ // bind the graphics object.. var projection = this.renderer.projection, offset = this.renderer.offset, - shader;// = this.renderer.shaderManager.primitiveShader; + shader;// = this.renderer.shaderManager.plugins.primitiveShader; if (webGLData.mode === 1) { - shader = this.renderer.shaderManager.complexPrimitiveShader; + shader = this.renderer.shaderManager.plugins.complexPrimitiveShader; this.renderer.shaderManager.setShader(shader); @@ -156,7 +156,7 @@ else { //this.renderer.shaderManager.activatePrimitiveShader(); - shader = this.renderer.shaderManager.primitiveShader; + shader = this.renderer.shaderManager.plugins.primitiveShader; this.renderer.shaderManager.setShader( shader ); gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); @@ -292,11 +292,11 @@ */ WebGLMaskManager.prototype.pushMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); if (maskData.dirty) { - this.renderer.objectRenderers.graphics.updateGraphics(maskData, this.renderer.gl); + this.renderer.plugins.graphics.updateGraphics(maskData, this.renderer.gl); } if (!maskData._webGL[this.renderer.gl.id].data.length) @@ -314,7 +314,7 @@ */ WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); this.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); }; diff --git a/src/core/renderers/webgl/managers/WebGLShaderManager.js b/src/core/renderers/webgl/managers/WebGLShaderManager.js index 90c6477..9ebb58e 100644 --- a/src/core/renderers/webgl/managers/WebGLShaderManager.js +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -1,9 +1,5 @@ var WebGLManager = require('./WebGLManager'), - PrimitiveShader = require('../shaders/PrimitiveShader'), - ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), - Shader = require('../shaders/Shader'), - FastShader = require('../shaders/FastShader'), - StripShader = require('../shaders/StripShader'); + utils = require('../../../utils'); /** * @class @@ -51,41 +47,16 @@ */ this.currentShader = null; - // this shader is used for rendering primitives - this.primitiveShader = null; - - // this shader is used for rendering triangle strips - this.complexPrimitiveShader = null; - - // this shader is used for the default sprite rendering - this.defaultShader = null; - - // this shader is used for the fast sprite rendering - this.fastShader = null; - - // the next one is used for rendering triangle strips - this.stripShader = null; + this.initPlugins(); // listen for context and update necessary shaders var self = this; - this.renderer.on('context', function (event) + this.renderer.on('context', function () { - var gl = event.data; - - // this shader is used for rendering primitives - self.primitiveShader = new PrimitiveShader(gl); - - // this shader is used for rendering triangle strips - self.complexPrimitiveShader = new ComplexPrimitiveShader(gl); - - // this shader is used for the default sprite rendering - self.defaultShader = new Shader(gl); - - // this shader is used for the fast sprite rendering - self.fastShader = new FastShader(gl); - - // the next one is used for rendering triangle strips - self.stripShader = new StripShader(gl); + for (var o in this.plugins) + { + this.plugins[o] = new (this.plugins[o].constructor)(self); + } self.setShader(self.defaultShader); }); @@ -95,6 +66,8 @@ WebGLShaderManager.prototype.constructor = WebGLShaderManager; module.exports = WebGLShaderManager; +utils.pluginTarget.mixin(WebGLShaderManager); + /** * Takes the attributes given in parameters. * @@ -164,24 +137,11 @@ */ WebGLShaderManager.prototype.destroy = function () { + this.destroyPlugins(); + this.attribState = null; this.tempAttribState = null; - this.primitiveShader.destroy(); - this.primitiveShader = null; - - this.complexPrimitiveShader.destroy(); - this.complexPrimitiveShader = null; - - this.defaultShader.destroy(); - this.defaultShader = null; - - this.fastShader.destroy(); - this.fastShader = null; - - this.stripShader.destroy(); - this.stripShader = null; - this.renderer = null; }; diff --git a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js b/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js deleted file mode 100644 index f8a07da..0000000 --- a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js +++ /dev/null @@ -1,53 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function ComplexPrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform vec3 tint;', - 'uniform float alpha;', - 'uniform vec3 color;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - color: { type: '3f', value: [0,0,0] }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); -ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; -module.exports = ComplexPrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/FastShader.js b/src/core/renderers/webgl/shaders/FastShader.js index d198f10..c80d040 100644 --- a/src/core/renderers/webgl/shaders/FastShader.js +++ b/src/core/renderers/webgl/shaders/FastShader.js @@ -1,15 +1,16 @@ -var Shader = require('./Shader'); +var Shader = require('./Shader'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @extends Shader * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. */ -function FastShader(gl) +function FastShader(shaderManager) { Shader.call(this, - gl, + shaderManager, // vertex shader [ 'attribute vec2 aVertexPosition;', @@ -70,3 +71,5 @@ FastShader.prototype = Object.create(Shader.prototype); FastShader.prototype.constructor = FastShader; module.exports = FastShader; + +WebGLShaderManager.registerPlugin('fastShader', FastShader); diff --git a/src/core/renderers/webgl/shaders/PrimitiveShader.js b/src/core/renderers/webgl/shaders/PrimitiveShader.js deleted file mode 100644 index 753c475..0000000 --- a/src/core/renderers/webgl/shaders/PrimitiveShader.js +++ /dev/null @@ -1,55 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform float alpha;', - 'uniform float flipY;', - 'uniform vec3 tint;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aColor:0 - } - ); -} - -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; -module.exports = PrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/Shader.js b/src/core/renderers/webgl/shaders/Shader.js index d065a76..c60c5f9 100644 --- a/src/core/renderers/webgl/shaders/Shader.js +++ b/src/core/renderers/webgl/shaders/Shader.js @@ -1,12 +1,16 @@ -var utils = require('../../../utils'); +var utils = require('../../../utils'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @namespace PIXI - * @param [fragmentSrc] {string} The source of the fragment shader. + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. * @param [vertexSrc] {string} The source of the vertex shader. + * @param [fragmentSrc] {string} The source of the fragment shader. + * @param customUniforms {object} Custom uniforms to use to augment the built-in ones. + * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Shader(gl, vertexSrc, fragmentSrc, customUniforms, customAttributes) +function Shader(shaderManager, vertexSrc, fragmentSrc, customUniforms, customAttributes) { /** * @member {number} @@ -18,7 +22,7 @@ * @member {WebGLContext} * @readonly */ - this.gl = gl; + this.gl = shaderManager.renderer.gl; /** * The WebGL program. @@ -31,8 +35,8 @@ uSampler: { type: 'sampler2D', value: 0 }, projectionVector: { type: '2f', value: { x: 0, y: 0 } }, offsetVector: { type: '2f', value: { x: 0, y: 0 } }, - projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, - 0, 1, 0, + projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, + 0, 1, 0, 0, 0, 1) }, dimensions: { type: '4f', value: new Float32Array(4) } }; @@ -103,6 +107,8 @@ Shader.prototype.constructor = Shader; module.exports = Shader; +WebGLShaderManager.registerPlugin('defaultShader', Shader); + Shader.prototype.init = function () { this.compile(); @@ -506,4 +512,4 @@ } return shader; -}; \ No newline at end of file +}; diff --git a/src/core/renderers/webgl/shaders/SpriteShader.js b/src/core/renderers/webgl/shaders/SpriteShader.js deleted file mode 100644 index 1805b0a..0000000 --- a/src/core/renderers/webgl/shaders/SpriteShader.js +++ /dev/null @@ -1,42 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function SpriteShader(gl) -{ - Shader.call(this, - gl, - null, - // fragment shader - [ - 'precision lowp float;', - - 'varying vec2 vTextureCoord;', - 'varying vec4 vColor;', - - 'uniform sampler2D uSampler;', - - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aTextureCoord: 0, - aColor: 0 - } - ); -} - -SpriteShader.prototype = Object.create(Shader.prototype); -SpriteShader.prototype.constructor = SpriteShader; -module.exports = SpriteShader; diff --git a/src/core/renderers/webgl/shaders/StripShader.js b/src/core/renderers/webgl/shaders/StripShader.js deleted file mode 100644 index f487381..0000000 --- a/src/core/renderers/webgl/shaders/StripShader.js +++ /dev/null @@ -1,54 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function StripShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - // 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform vec2 projectionVector;', - 'uniform vec2 offsetVector;', - - 'varying vec2 vTextureCoord;', - - 'void main(void){', - ' vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);', - ' v -= offsetVector.xyx;', - ' gl_Position = vec4( v.x / projectionVector.x -1.0, v.y / -projectionVector.y + 1.0 , 0.0, 1.0);', - ' vTextureCoord = aTextureCoord;', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'uniform float alpha;', - 'uniform sampler2D uSampler;', - - 'varying vec2 vTextureCoord;', - - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y)) * alpha;', - '}' - ].join('\n'), - // custom uniforms - { - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -StripShader.prototype = Object.create(Shader.prototype); -StripShader.prototype.constructor = StripShader; -module.exports = StripShader; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index 0a34e55..43e2d00 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -33,7 +33,7 @@ * @property texture * @type Any */ - this.texture = null + this.texture = null; this.width = 0; this.height = 0; @@ -56,11 +56,11 @@ this.frameBuffer = gl.createFramebuffer(); - /* + /* A frame buffer needs a target to render to.. create a texture and bind it attach it to the framebuffer.. */ - + this.texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, this.texture); @@ -70,13 +70,13 @@ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); 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); - + gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer ); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0); - - /* + + /* The stencil buffer is used for masking in pixi lets create one and then add attach it to the framebuffer.. */ @@ -86,7 +86,7 @@ } this.resize(width, height); - + }; RenderTarget.prototype.constructor = RenderTarget; @@ -94,21 +94,21 @@ /** * Clears the filter texture. -* +* * @method clear */ RenderTarget.prototype.clear = function() { var gl = this.gl; - + gl.clearColor(0,0,0, 0); gl.clear(gl.COLOR_BUFFER_BIT); }; RenderTarget.prototype.activate = function() { - this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer ); -} + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer); +}; /** * Resizes the texture to the specified width and height @@ -119,25 +119,25 @@ */ RenderTarget.prototype.resize = function(width, height) { - width = width | 0; - height = height | 0; - - if(this.width === width && this.height === height) return; + if (this.width === width && this.height === height) { + return; + } this.width = width; this.height = height; + this.projectionMatrix = new math.Matrix(); - - if(!this.root) + + if (!this.root) { var gl = this.gl; this.projectionMatrix.a = 1/width*2; - this.projectionMatrix.d = 1/height*2; + this.projectionMatrix.d = -1/height*2; this.projectionMatrix.tx = -1; - this.projectionMatrix.ty = -1; + this.projectionMatrix.ty = 1; gl.bindTexture(gl.TEXTURE_2D, this.texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width , height , 0, gl.RGBA, gl.UNSIGNED_BYTE, null); @@ -157,7 +157,7 @@ /** * Destroys the filter texture. -* +* * @method destroy */ RenderTarget.prototype.destroy = function() diff --git a/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js b/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js index a63f4d7..3f37b7d 100644 --- a/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js +++ b/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js @@ -183,7 +183,7 @@ */ WebGLFastSpriteBatch.prototype.begin = function (spriteBatch) { - this.shader = this.renderer.shaderManager.fastShader; + this.shader = this.renderer.shaderManager.plugins.fastShader; this.matrix = spriteBatch.worldTransform.toArray(true); diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index bf8db28..2dc47ff 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -201,8 +201,8 @@ Sprite.prototype._renderWebGL = function (renderer) { - renderer.setObjectRenderer(renderer.objectRenderers.sprite); - renderer.objectRenderers.sprite.render(this); + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); }; /** diff --git a/src/core/sprites/SpriteBatch.js b/src/core/sprites/SpriteBatch.js index 63cebc0..4fe33e4 100644 --- a/src/core/sprites/SpriteBatch.js +++ b/src/core/sprites/SpriteBatch.js @@ -61,7 +61,7 @@ renderer.spriteBatch.stop(); - renderer.shaderManager.setShader(renderer.shaderManager.fastShader); + renderer.shaderManager.setShader(renderer.shaderManager.plugins.fastShader); renderer.fastSpriteBatch.begin(this); renderer.fastSpriteBatch.render(this); diff --git a/src/core/sprites/SpriteRenderer.js b/src/core/sprites/SpriteRenderer.js deleted file mode 100644 index fd9a802..0000000 --- a/src/core/sprites/SpriteRenderer.js +++ /dev/null @@ -1,537 +0,0 @@ -var ObjectRenderer = require('../renderers/webgl/utils/ObjectRenderer'), - Shader = require('../renderers/webgl/shaders/Shader'), - SpriteShader = require('../renderers/webgl/shaders/SpriteShader'), - WebGLRenderer = require('../renderers/webgl/WebGLRenderer'); - math = require('../math'); - -/** - * @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.setupContext(); -} - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; -module.exports = 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 = sprite; - 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 - gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, this.renderer.currentRenderTarget.projectionMatrix.toArray(true)); - } - } - - 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; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js new file mode 100644 index 0000000..c006ba4 --- /dev/null +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -0,0 +1,533 @@ +var ObjectRenderer = require('../../renderers/webgl/utils/ObjectRenderer'), + Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLRenderer = require('../../renderers/webgl/WebGLRenderer'), + SpriteShader = require('./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) +{ + 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; + + this.setupContext(); +} + +SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); +SpriteRenderer.prototype.constructor = SpriteRenderer; +module.exports = SpriteRenderer; + +WebGLRenderer.registerPlugin('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(this.renderer.shaderManager); + + // 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(this.renderer.shaderManager, 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 + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, this.renderer.currentRenderTarget.projectionMatrix.toArray(true)); + } + } + + 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; +}; diff --git a/src/core/sprites/webgl/SpriteShader.js b/src/core/sprites/webgl/SpriteShader.js new file mode 100644 index 0000000..3925e9f --- /dev/null +++ b/src/core/sprites/webgl/SpriteShader.js @@ -0,0 +1,45 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function SpriteShader(shaderManager) +{ + Shader.call(this, + shaderManager, + null, + // fragment shader + [ + 'precision lowp float;', + + 'varying vec2 vTextureCoord;', + 'varying vec4 vColor;', + + 'uniform sampler2D uSampler;', + + 'void main(void){', + ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aTextureCoord: 0, + aColor: 0 + } + ); +} + +SpriteShader.prototype = Object.create(Shader.prototype); +SpriteShader.prototype.constructor = SpriteShader; +module.exports = SpriteShader; + +WebGLShaderManager.registerPlugin('fastShader', SpriteShader); diff --git a/src/core/utils/index.js b/src/core/utils/index.js index bfae7ae..1d451c0 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -7,9 +7,10 @@ _uid: 0, _saidHello: false, - PolyK: require('./PolyK'), - EventData: require('./EventData'), - eventTarget: require('./eventTarget'), + PolyK: require('./PolyK'), + EventData: require('./EventData'), + eventTarget: require('./eventTarget'), + pluginTarget: require('./pluginTarget'), /** * Gets the next uuid diff --git a/src/core/utils/pluginTarget.js b/src/core/utils/pluginTarget.js new file mode 100644 index 0000000..885fe0e --- /dev/null +++ b/src/core/utils/pluginTarget.js @@ -0,0 +1,37 @@ +/** + * Mixins functionality to make an object have "plugins". + * + * @mixin + * @namespace PIXI + * @param obj {object} The object to mix into. + * @example + * function MyObject() {} + * + * pluginTarget.mixin(MyObject); + */ +function pluginTarget(obj) { + obj.__plugins = {}; + + obj.registerPlugin = function (pluginName, ctor) { + obj.__plugins[pluginName] = ctor; + }; + + obj.prototype.initPlugins = function () { + this.plugins = {}; + + for (var o in obj.__plugins) { + this.plugins[o] = new (obj.__plugins[o])(this); + } + }; + + obj.prototype.destroyPlugins = function () { + for (var o in this.plugins) { + this.plugins[o].destroy(); + this.plugins[o] = null; + } + + this.plugins = null; + }; +} + +module.exports = pluginTarget; diff --git a/src/extras/Strip.js b/src/extras/Strip.js index 57691b5..6bf78cb 100644 --- a/src/extras/Strip.js +++ b/src/extras/Strip.js @@ -89,7 +89,7 @@ this._initWebGL(renderer); } - renderer.shaderManager.setShader(renderer.shaderManager.stripShader); + renderer.shaderManager.setShader(renderer.shaderManager.plugins.stripShader); this._renderStrip(renderer); @@ -128,7 +128,7 @@ var gl = renderer.gl; var projection = renderer.projection, offset = renderer.offset, - shader = renderer.shaderManager.stripShader; + shader = renderer.shaderManager.plugins.stripShader; var drawMode = this.drawMode === Strip.DrawModes.TRIANGLE_STRIP ? gl.TRIANGLE_STRIP : gl.TRIANGLES; diff --git a/src/extras/StripShader.js b/src/extras/StripShader.js new file mode 100644 index 0000000..3e7c47f --- /dev/null +++ b/src/extras/StripShader.js @@ -0,0 +1,56 @@ +var core = require('../core'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function StripShader(shaderManager) +{ + core.Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + // 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform vec2 projectionVector;', + 'uniform vec2 offsetVector;', + + 'varying vec2 vTextureCoord;', + + 'void main(void){', + ' vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);', + ' v -= offsetVector.xyx;', + ' gl_Position = vec4( v.x / projectionVector.x -1.0, v.y / -projectionVector.y + 1.0 , 0.0, 1.0);', + ' vTextureCoord = aTextureCoord;', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'uniform float alpha;', + 'uniform sampler2D uSampler;', + + 'varying vec2 vTextureCoord;', + + 'void main(void){', + ' gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y)) * alpha;', + '}' + ].join('\n'), + // custom uniforms + { + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +StripShader.prototype = Object.create(core.Shader.prototype); +StripShader.prototype.constructor = StripShader; +module.exports = StripShader; + +core.WebGLShaderManager.registerPlugin('stripShader', StripShader); diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 61d5d54..131c6b7 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -181,9 +181,9 @@ texture._frame.width = this.width; texture._frame.height = this.height; - renderer.setObjectRenderer(renderer.objectRenderers.sprite); - renderer.objectRenderers.sprite.render(this); - + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + texture._uvs = tempUvs; texture._frame.width = tempWidth; texture._frame.height = tempHeight; diff --git a/src/core/display/DisplayObjectContainer.js b/src/core/display/DisplayObjectContainer.js index 88a88fc..120c9ff 100644 --- a/src/core/display/DisplayObjectContainer.js +++ b/src/core/display/DisplayObjectContainer.js @@ -500,8 +500,8 @@ // push filter first as we need to ensure the stencil buffer is correct for any masking if (this._filters) { - renderer.objectRenderers.sprite.flush(); - renderer.filterManager.pushFilter(this, this._filters)//this._filterBlock); + renderer.plugins.sprite.flush(); + renderer.filterManager.pushFilter(this._filterBlock); } if (this._mask) diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js new file mode 100644 index 0000000..ccd4a91 --- /dev/null +++ b/src/core/graphics/Graphics.js @@ -0,0 +1,1116 @@ +var DisplayObjectContainer = require('../display/DisplayObjectContainer'), + Sprite = require('../sprites/Sprite'), + Texture = require('../textures/Texture'), + CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), + CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), + // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), + GraphicsData = require('./GraphicsData'), + math = require('../math'), + CONST = require('../const'); + +/** + * The Graphics class contains methods used to draw primitive shapes such as lines, circles and + * rectangles to the display, and color and fill them. + * + * @class + * @extends DisplayObjectContainer + * @namespace PIXI + */ +function Graphics() +{ + DisplayObjectContainer.call(this); + + this.renderable = true; + + /** + * The alpha value used when filling the Graphics object. + * + * @member {number} + * @default 1 + */ + this.fillAlpha = 1; + + /** + * The width (thickness) of any lines drawn. + * + * @member {number} + * @default 0 + */ + this.lineWidth = 0; + + /** + * The color of any lines drawn. + * + * @member {string} + * @default 0 + */ + this.lineColor = 0; + + /** + * Graphics data + * + * @member {GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. + * + * @member {number} + * @default CONST.blendModes.NORMAL; + */ + this.blendMode = CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {Rectangle} + * @private + */ + this._localBounds = new math.Rectangle(0,0,1,1); + + /** + * Used to detect if the graphics object has changed. If this is set to true then the graphics + * object will be recalculated. + * + * @member {boolean} + * @private + */ + this.dirty = true; + + /** + * Used to detect if the WebGL graphics object has changed. If this is set to true then the + * graphics object will be recalculated. + * + * @member {boolean} + * @private + */ + this.glDirty = false; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; +} + +// constructor +Graphics.prototype = Object.create(DisplayObjectContainer.prototype); +Graphics.prototype.constructor = Graphics; +module.exports = Graphics; + +Object.defineProperties(Graphics.prototype, { + /** + * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. + * This is useful if your graphics element does not change often, as it will speed up the rendering + * of the object in exchange for taking up texture memory. It is also useful if you need the graphics + * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if + * you are constantly redrawing the graphics element. + * + * @member {boolean} + * @memberof Graphics# + * @default false + * @private + */ + cacheAsBitmap: { + get: function () + { + return this._cacheAsBitmap; + }, + set: function (value) + { + this._cacheAsBitmap = value; + + if (this._cacheAsBitmap) + { + this._generateCachedSprite(); + } + else + { + this.destroyCachedSprite(); + this.dirty = true; + } + } + } +}); + +/** + * Creates a new Graphics object with the same values as this one. + * + * @return {Graphics} + */ +GraphicsData.prototype.clone = function () +{ + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = this.dirty; + clone.glDirty = this.glDirty; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData.clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; +}; + +/** + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. + * + * @param lineWidth {number} width of the line to draw, will update the objects stored style + * @param color {number} color of the line to draw, will update the objects stored style + * @param alpha {number} alpha of the line to draw, will update the objects stored style + * @return {Graphics} + */ +Graphics.prototype.lineStyle = function (lineWidth, color, alpha) +{ + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (arguments.length < 3) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); + } + else + { + // otherwise its empty so lets just set the line properties + this.currentPath.lineWidth = this.lineWidth; + this.currentPath.lineColor = this.lineColor; + this.currentPath.lineAlpha = this.lineAlpha; + } + } + + return this; +}; + +/** + * Moves the current drawing position to x, y. + * + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {Graphics} + */ +Graphics.prototype.moveTo = function (x, y) +{ + this.drawShape(new math.Polygon([x,y])); + + return this; +}; + +/** + * Draws a line using the current line style from the current drawing position to (x, y); + * The current drawing position is then set to (x, y). + * + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {Graphics} + */ +Graphics.prototype.lineTo = function (x, y) +{ + this.currentPath.shape.points.push(x, y); + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a quadratic bezier curve and then draws it. + * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c + * + * @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 {Graphics} + */ +Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var xa, + ya, + n = 20, + points = this.currentPath.shape.points; + + if (points.length === 0) + { + this.moveTo(0, 0); + } + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + for (var i = 1; i <= n; ++i) + { + j = i / n; + + xa = fromX + ( (cpX - fromX) * j ); + ya = fromY + ( (cpY - fromY) * j ); + + points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), + ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); + } + + this.dirty = true; + + return this; +}; + +/** + * Calculate the points for a bezier curve and then draws it. + * + * @param cpX {number} Control point x + * @param cpY {number} Control point y + * @param cpX2 {number} Second Control point x + * @param cpY2 {number} Second Control point y + * @param toX {number} Destination point x + * @param toY {number} Destination point y + * @return {Graphics} + */ +Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points = [0, 0]; + } + } + else + { + this.moveTo(0,0); + } + + var n = 20, + dt, + dt2, + dt3, + t2, + t3, + points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + var j = 0; + + for (var i = 1; i <= n; ++i) + { + j = i / n; + + dt = (1 - j); + dt2 = dt * dt; + dt3 = dt2 * dt; + + t2 = j * j; + t3 = t2 * j; + + points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, + dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); + } + + this.dirty = true; + + return this; +}; + +/** + * The arcTo() method creates an arc/curve between two tangents on the canvas. + * + * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! + * + * @param x1 {number} The x-coordinate of the beginning of the arc + * @param y1 {number} The y-coordinate of the beginning of the arc + * @param x2 {number} The x-coordinate of the end of the arc + * @param y2 {number} The y-coordinate of the end of the arc + * @param radius {number} The radius of the arc + * @return {Graphics} + */ +Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) +{ + if (this.currentPath) + { + if (this.currentPath.shape.points.length === 0) + { + this.currentPath.shape.points.push(x1, y1); + } + } + else + { + this.moveTo(x1, y1); + } + + var points = this.currentPath.shape.points, + fromX = points[points.length-2], + fromY = points[points.length-1], + a1 = fromY - y1, + b1 = fromX - x1, + a2 = y2 - y1, + b2 = x2 - x1, + mm = Math.abs(a1 * b2 - b1 * a2); + + if (mm < 1.0e-8 || radius === 0) + { + if (points[points.length-2] !== x1 || points[points.length-1] !== y1) + { + points.push(x1, y1); + } + } + else + { + var dd = a1 * a1 + b1 * b1, + cc = a2 * a2 + b2 * b2, + tt = a1 * a2 + b1 * b2, + k1 = radius * Math.sqrt(dd) / mm, + k2 = radius * Math.sqrt(cc) / mm, + j1 = k1 * tt / dd, + j2 = k2 * tt / cc, + cx = k1 * b2 + k2 * b1, + cy = k1 * a2 + k2 * a1, + px = b1 * (k2 + j1), + py = a1 * (k2 + j1), + qx = b2 * (k1 + j2), + qy = a2 * (k1 + j2), + startAngle = Math.atan2(py - cy, px - cx), + endAngle = Math.atan2(qy - cy, qx - cx); + + this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); + } + + this.dirty = true; + + return this; +}; + +/** + * The arc method creates an arc/curve (used to create circles, or parts of circles). + * + * @param cx {number} The x-coordinate of the center of the circle + * @param cy {number} The y-coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) + * @param endAngle {number} The ending angle, in radians + * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {Graphics} + */ +Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) +{ + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + var points; + + // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... + if (this.currentPath) + { + points = this.currentPath.shape.points; + + if (points.length === 0) + { + points.push(startX, startY); + } + else if (points[points.length-2] !== startX || points[points.length-1] !== startY) + { + points.push(startX, startY); + } + } + else + { + this.moveTo(startX, startY); + points = this.currentPath.shape.points; + } + + if (startAngle === endAngle) + { + return this; + } + + if (!anticlockwise && endAngle <= startAngle) + { + endAngle += Math.PI * 2; + } + else if (anticlockwise && startAngle <= endAngle) + { + startAngle += Math.PI * 2; + } + + var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); + var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; + + if (sweep === 0) + { + return this; + } + + var theta = sweep/(segs*2); + var theta2 = theta*2; + + var cTheta = Math.cos(theta); + var sTheta = Math.sin(theta); + + var segMinus = segs - 1; + + var remainder = ( segMinus % 1 ) / segMinus; + + for (var i = 0; i <= segMinus; ++i) + { + var real = i + remainder * i; + var angle = ((theta) + startAngle + (theta2 * real)); + + var c = Math.cos(angle); + var s = -Math.sin(angle); + + points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, + ( (cTheta * -s) + (sTheta * c) ) * radius + cy); + } + + this.dirty = true; + + return this; +}; + +/** + * Specifies a simple one-color fill that subsequent calls to other Graphics methods + * (such as lineTo() or drawCircle()) use when drawing. + * + * @param color {number} the color of the fill + * @param alpha {number} the alpha of the fill + * @return {Graphics} + */ +Graphics.prototype.beginFill = function (color, alpha) +{ + this.filling = true; + this.fillColor = color || 0; + this.fillAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length <= 2) + { + this.currentPath.fill = this.filling; + this.currentPath.fillColor = this.fillColor; + this.currentPath.fillAlpha = this.fillAlpha; + } + } + return this; +}; + +/** + * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. + * + * @return {Graphics} + */ +Graphics.prototype.endFill = function () +{ + this.filling = false; + this.fillColor = null; + this.fillAlpha = 1; + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @return {Graphics} + */ +Graphics.prototype.drawRect = function ( x, y, width, height ) +{ + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; +}; + +/** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + */ +Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) +{ + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; +}; + +/** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {Graphics} + */ +Graphics.prototype.drawCircle = function (x, y, radius) +{ + this.drawShape(new math.Circle(x,y, radius)); + + return this; +}; + +/** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {Graphics} + */ +Graphics.prototype.drawEllipse = function (x, y, width, height) +{ + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; +}; + +/** + * Draws a polygon using the given path. + * + * @param path {Array} The path data used to construct the polygon. + * @return {Graphics} + */ +Graphics.prototype.drawPolygon = function (path) +{ + if (!(path instanceof Array)) + { + path = Array.prototype.slice.call(arguments); + } + + this.drawShape(new math.Polygon(path)); + + return this; +}; + +/** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {Graphics} + */ +Graphics.prototype.clear = function () +{ + this.lineWidth = 0; + this.filling = false; + + this.dirty = true; + this.clearDirty = true; + this.graphicsData = []; + + return this; +}; + +/** + * Useful function that returns a texture of the graphics object that can then be used to create sprites + * This can be quite useful if your geometry is complicated and needs to be reused multiple times. + * + * @param resolution {number} The resolution of the texture being generated + * @param scaleMode {number} Should be one of the scaleMode consts + * @return {Texture} a texture of the graphics object + */ +Graphics.prototype.generateTexture = function (resolution, scaleMode) +{ + resolution = resolution || 1; + + var bounds = this.getBounds(); + + var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @param renderer {WebGLRenderer} + */ +Graphics.prototype._renderWebGL = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (this.isMask === true) + { + return; + } + + // this code may still be needed so leaving for now.. + // + /* + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); + + return; + } + + */ + + if (this.glDirty) + { + this.dirty = true; + this.glDirty = false; + } + + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + +}; + +/** + * Renders the object using the Canvas renderer + * + * @param renderer {CanvasRenderer} + * @private + */ +Graphics.prototype.renderCanvas = function (renderer) +{ + // if the sprite is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.alpha <= 0 || this.isMask === true) + { + return; + } + + if (this._cacheAsBitmap) + { + if (this.dirty || this.cachedSpriteDirty) + { + this._generateCachedSprite(); + + // we will also need to update the texture + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.alpha = this.alpha; + + Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); + + return; + } + else + { + var context = renderer.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderer.currentBlendMode) + { + renderer.currentBlendMode = this.blendMode; + context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask, renderer); + } + + var resolution = renderer.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } +}; + +/** + * Retrieves the bounds of the graphic shape as a rectangle object + * + * @return {Rectangle} the rectangular bounding area + */ +Graphics.prototype.getBounds = function (matrix) +{ + // return an empty object if the item is a mask! + if (this.isMask) + { + return math.Rectangle.EMPTY; + } + + if (this.dirty) + { + this.updateLocalBounds(); + + this.glDirty = true; + this.cachedSpriteDirty = true; + this.dirty = false; + } + + var bounds = this._localBounds; + + var w0 = bounds.x; + var w1 = bounds.width + bounds.x; + + var h0 = bounds.y; + var h1 = bounds.height + bounds.y; + + var worldTransform = matrix || this.worldTransform; + + var a = worldTransform.a; + var b = worldTransform.b; + var c = worldTransform.c; + var d = worldTransform.d; + var tx = worldTransform.tx; + var ty = worldTransform.ty; + + var x1 = a * w1 + c * h1 + tx; + var y1 = d * h1 + b * w1 + ty; + + var x2 = a * w0 + c * h1 + tx; + var y2 = d * h1 + b * w0 + ty; + + var x3 = a * w0 + c * h0 + tx; + var y3 = d * h0 + b * w0 + ty; + + var x4 = a * w1 + c * h0 + tx; + var y4 = d * h0 + b * w1 + ty; + + var maxX = x1; + var maxY = y1; + + var minX = x1; + var minY = y1; + + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + this._bounds.x = minX; + this._bounds.width = maxX - minX; + + this._bounds.y = minY; + this._bounds.height = maxY - minY; + + return this._bounds; +}; + +/** + * Update the bounds of the object + * + */ +Graphics.prototype.updateLocalBounds = function () +{ + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.x = minX - padding; + this._localBounds.width = (maxX - minX) + padding * 2; + + this._localBounds.y = minY - padding; + this._localBounds.height = (maxY - minY) + padding * 2; +}; + +/** + * Generates the cached sprite when the sprite has cacheAsBitmap = true + * + * @private + */ +Graphics.prototype._generateCachedSprite = function () +{ + var bounds = this.getLocalBounds(); + + if (!this._cachedSprite) + { + var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); + var texture = Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new Sprite(texture); + this._cachedSprite.buffer = canvasBuffer; + + this._cachedSprite.worldTransform = this.worldTransform; + } + else + { + this._cachedSprite.buffer.resize(bounds.width, bounds.height); + } + + // leverage the anchor to account for the offset of the element + this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); + this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); + + // this._cachedSprite.buffer.context.save(); + this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); + + // make sure we set the alpha of the graphics to 1 for the render.. + this.worldAlpha = 1; + + // now render the graphic.. + CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); + + this._cachedSprite.alpha = this.alpha; +}; + +/** + * Updates texture size based on canvas size + * + * @private + */ +Graphics.prototype.updateCachedSpriteTexture = function () +{ + var cachedSprite = this._cachedSprite; + var texture = cachedSprite.texture; + var canvas = cachedSprite.buffer.canvas; + + texture.baseTexture.width = canvas.width; + texture.baseTexture.height = canvas.height; + texture.crop.width = texture.frame.width = canvas.width; + texture.crop.height = texture.frame.height = canvas.height; + + cachedSprite._width = canvas.width; + cachedSprite._height = canvas.height; + + // update the dirty base textures + texture.baseTexture.dirty(); +}; + +/** + * Destroys a previous cached sprite. + * + */ +Graphics.prototype.destroyCachedSprite = function () +{ + this._cachedSprite.texture.destroy(true); + + // let the gc collect the unused sprite + // TODO could be object pooled! + this._cachedSprite = null; +}; + +/** + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. + * + * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. + * @return {GraphicsData} The generated GraphicsData object. + */ +Graphics.prototype.drawShape = function (shape) +{ + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js new file mode 100644 index 0000000..788f6f2 --- /dev/null +++ b/src/core/graphics/GraphicsData.js @@ -0,0 +1,42 @@ +/** + * A GraphicsData object. + * + * @class + * @namespace PIXI + */ +function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) +{ + this.lineWidth = lineWidth; + this.lineColor = lineColor; + this.lineAlpha = lineAlpha; + this._lineTint = lineColor; + + this.fillColor = fillColor; + this.fillAlpha = fillAlpha; + this._fillTint = fillColor; + this.fill = fill; + + this.shape = shape; + this.type = shape.type; +} + +GraphicsData.prototype.constructor = GraphicsData; +module.exports = GraphicsData; + +/** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {GraphicsData} + */ +GraphicsData.prototype.clone = function () +{ + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); +}; diff --git a/src/core/graphics/webgl/ComplexPrimitiveShader.js b/src/core/graphics/webgl/ComplexPrimitiveShader.js new file mode 100644 index 0000000..1ca0c24 --- /dev/null +++ b/src/core/graphics/webgl/ComplexPrimitiveShader.js @@ -0,0 +1,56 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function ComplexPrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 color;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + color: { type: '3f', value: [0,0,0] }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); +ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; +module.exports = ComplexPrimitiveShader; + +WebGLShaderManager.registerPlugin('complexPrimitiveShader', ComplexPrimitiveShader); diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js new file mode 100644 index 0000000..c81350b --- /dev/null +++ b/src/core/graphics/webgl/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.registerPlugin('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 shader = renderer.shaderManager.plugins.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 ); + +// var matrix = graphics.worldTransform.clone(); +// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); +// matrix.append(graphics.worldTransform); + + for (var i = 0; i < webGL.data.length; i++) + { + if (webGL.data[i].mode === 1) + { + webGLData = webGL.data[i]; + + renderer.maskManager.pushStencil(graphics, webGLData, renderer); + + // render quad.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + renderer.maskManager.popStencil(graphics, webGLData, renderer); + } + else + { + webGLData = webGL.data[i]; + + + renderer.shaderManager.setShader( shader );//activatePrimitiveShader(); + shader = renderer.shaderManager.plugins.primitiveShader; + + gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); + + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); + + 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/graphics/webgl/PrimitiveShader.js b/src/core/graphics/webgl/PrimitiveShader.js new file mode 100644 index 0000000..b7c5766 --- /dev/null +++ b/src/core/graphics/webgl/PrimitiveShader.js @@ -0,0 +1,58 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function PrimitiveShader(shaderManager) +{ + Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', + + 'uniform float alpha;', + 'uniform float flipY;', + 'uniform vec3 tint;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'varying vec4 vColor;', + + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aColor:0 + } + ); +} + +PrimitiveShader.prototype = Object.create(Shader.prototype); +PrimitiveShader.prototype.constructor = PrimitiveShader; +module.exports = PrimitiveShader; + +WebGLShaderManager.registerPlugin('primitiveShader', PrimitiveShader); diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js new file mode 100644 index 0000000..41be102 --- /dev/null +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -0,0 +1,48 @@ +/** + * @class + * @private + */ +function WebGLGraphicsData(gl) { + this.gl = gl; + + //TODO does this need to be split before uploding?? + this.color = [0,0,0]; // color split! + this.points = []; + this.indices = []; + this.buffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + this.mode = 1; + this.alpha = 1; + this.dirty = true; +} + +WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; +module.exports = WebGLGraphicsData; + +/** + * + */ +WebGLGraphicsData.prototype.reset = function () { + this.points = []; + this.indices = []; +}; + +/** + * + */ +WebGLGraphicsData.prototype.upload = function () { + var gl = this.gl; + +// this.lastIndex = graphics.graphicsData.length; + this.glPoints = new Float32Array(this.points); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); + + this.glIndicies = new Uint16Array(this.indices); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); + + this.dirty = false; +}; diff --git a/src/core/index.js b/src/core/index.js index 6f744e9..1d0a398 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -23,12 +23,12 @@ Sprite: require('./sprites/Sprite'), SpriteBatch: require('./sprites/SpriteBatch'), - SpriteRenderer: require('./sprites/SpriteRenderer'), + SpriteRenderer: require('./sprites/webgl/SpriteRenderer'), // primitives - Graphics: require('./primitives/Graphics'), - GraphicsData: require('./primitives/GraphicsData'), - GraphicsRenderer: require('./primitives/GraphicsRenderer'), + Graphics: require('./graphics/Graphics'), + GraphicsData: require('./graphics/GraphicsData'), + GraphicsRenderer: require('./graphics/webgl/GraphicsRenderer'), // textures Texture: require('./textures/Texture'), @@ -42,10 +42,8 @@ CanvasBuffer: require('./renderers/canvas/utils/CanvasBuffer'), // renderers - webgl - WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), - // WebGLGraphics: require('./renderers/webgl/utils/WebGLGraphics'), - - + WebGLRenderer: require('./renderers/webgl/WebGLRenderer'), + Shader: require('./renderers/webgl/shaders/Shader'), /** * This helper function will automatically detect which renderer you should be using. @@ -103,7 +101,3 @@ return core.autoDetectRenderer(width, height, options, isAndroid); } }; - -// Adding here for now.. sure there is a better place! -core.WebGLRenderer.registerObjectRenderer('sprite', core.SpriteRenderer); -core.WebGLRenderer.registerObjectRenderer('graphics', core.GraphicsRenderer); diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index a086950..81de05c 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -268,6 +268,6 @@ matrix.ty = this.ty; return matrix; -} +}; Matrix.IDENTITY = new Matrix(); diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js deleted file mode 100644 index 8db9b45..0000000 --- a/src/core/primitives/Graphics.js +++ /dev/null @@ -1,1116 +0,0 @@ -var DisplayObjectContainer = require('../display/DisplayObjectContainer'), - //Sprite = require('../display/Sprite'), - Texture = require('../textures/Texture'), - CanvasBuffer = require('../renderers/canvas/utils/CanvasBuffer'), - CanvasGraphics = require('../renderers/canvas/utils/CanvasGraphics'), - // WebGLGraphics = require('../renderers/webgl/utils/WebGLGraphics'), - GraphicsData = require('./GraphicsData'), - math = require('../math'), - CONST = require('../const'); - -/** - * The Graphics class contains methods used to draw primitive shapes such as lines, circles and - * rectangles to the display, and color and fill them. - * - * @class - * @extends DisplayObjectContainer - * @namespace PIXI - */ -function Graphics() -{ - DisplayObjectContainer.call(this); - - this.renderable = true; - - /** - * The alpha value used when filling the Graphics object. - * - * @member {number} - * @default 1 - */ - this.fillAlpha = 1; - - /** - * The width (thickness) of any lines drawn. - * - * @member {number} - * @default 0 - */ - this.lineWidth = 0; - - /** - * The color of any lines drawn. - * - * @member {string} - * @default 0 - */ - this.lineColor = 0; - - /** - * Graphics data - * - * @member {GraphicsData[]} - * @private - */ - this.graphicsData = []; - - /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. - * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of blendModes.NORMAL to reset the blend mode. - * - * @member {number} - * @default CONST.blendModes.NORMAL; - */ - this.blendMode = CONST.blendModes.NORMAL; - - /** - * Current path - * - * @member {GraphicsData} - * @private - */ - this.currentPath = null; - - /** - * Array containing some WebGL-related properties used by the WebGL renderer. - * - * @member {object} - * @private - */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; - - /** - * Whether this shape is being used as a mask. - * - * @member {boolean} - */ - this.isMask = false; - - /** - * The bounds' padding used for bounds calculation. - * - * @member {number} - */ - this.boundsPadding = 0; - - /** - * A cache of the local bounds to prevent recalculation. - * - * @member {Rectangle} - * @private - */ - this._localBounds = new math.Rectangle(0,0,1,1); - - /** - * Used to detect if the graphics object has changed. If this is set to true then the graphics - * object will be recalculated. - * - * @member {boolean} - * @private - */ - this.dirty = true; - - /** - * Used to detect if the WebGL graphics object has changed. If this is set to true then the - * graphics object will be recalculated. - * - * @member {boolean} - * @private - */ - this.glDirty = false; - - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; -} - -// constructor -Graphics.prototype = Object.create(DisplayObjectContainer.prototype); -Graphics.prototype.constructor = Graphics; -module.exports = Graphics; - -Object.defineProperties(Graphics.prototype, { - /** - * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite. - * This is useful if your graphics element does not change often, as it will speed up the rendering - * of the object in exchange for taking up texture memory. It is also useful if you need the graphics - * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if - * you are constantly redrawing the graphics element. - * - * @member {boolean} - * @memberof Graphics# - * @default false - * @private - */ - cacheAsBitmap: { - get: function () - { - return this._cacheAsBitmap; - }, - set: function (value) - { - this._cacheAsBitmap = value; - - if (this._cacheAsBitmap) - { - this._generateCachedSprite(); - } - else - { - this.destroyCachedSprite(); - this.dirty = true; - } - } - } -}); - -/** - * Creates a new Graphics object with the same values as this one. - * - * @return {Graphics} - */ -GraphicsData.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = this.dirty; - clone.glDirty = this.glDirty; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData.clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {Graphics} - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (arguments.length < 3) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - this.drawShape( new math.Polygon( this.currentPath.shape.points.slice(-2) )); - } - else - { - // otherwise its empty so lets just set the line properties - this.currentPath.lineWidth = this.lineWidth; - this.currentPath.lineColor = this.lineColor; - this.currentPath.lineAlpha = this.lineAlpha; - } - } - - return this; -}; - -/** - * Moves the current drawing position to x, y. - * - * @param x {number} the X coordinate to move to - * @param y {number} the Y coordinate to move to - * @return {Graphics} - */ -Graphics.prototype.moveTo = function (x, y) -{ - this.drawShape(new math.Polygon([x,y])); - - return this; -}; - -/** - * Draws a line using the current line style from the current drawing position to (x, y); - * The current drawing position is then set to (x, y). - * - * @param x {number} the X coordinate to draw to - * @param y {number} the Y coordinate to draw to - * @return {Graphics} - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a quadratic bezier curve and then draws it. - * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c - * - * @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 {Graphics} - */ -Graphics.prototype.quadraticCurveTo = function (cpX, cpY, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var xa, - ya, - n = 20, - points = this.currentPath.shape.points; - - if (points.length === 0) - { - this.moveTo(0, 0); - } - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - for (var i = 1; i <= n; ++i) - { - j = i / n; - - xa = fromX + ( (cpX - fromX) * j ); - ya = fromY + ( (cpY - fromY) * j ); - - points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ), - ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) ); - } - - this.dirty = true; - - return this; -}; - -/** - * Calculate the points for a bezier curve and then draws it. - * - * @param cpX {number} Control point x - * @param cpY {number} Control point y - * @param cpX2 {number} Second Control point x - * @param cpY2 {number} Second Control point y - * @param toX {number} Destination point x - * @param toY {number} Destination point y - * @return {Graphics} - */ -Graphics.prototype.bezierCurveTo = function (cpX, cpY, cpX2, cpY2, toX, toY) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points = [0, 0]; - } - } - else - { - this.moveTo(0,0); - } - - var n = 20, - dt, - dt2, - dt3, - t2, - t3, - points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - var j = 0; - - for (var i = 1; i <= n; ++i) - { - j = i / n; - - dt = (1 - j); - dt2 = dt * dt; - dt3 = dt2 * dt; - - t2 = j * j; - t3 = t2 * j; - - points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX, - dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY); - } - - this.dirty = true; - - return this; -}; - -/** - * The arcTo() method creates an arc/curve between two tangents on the canvas. - * - * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google! - * - * @param x1 {number} The x-coordinate of the beginning of the arc - * @param y1 {number} The y-coordinate of the beginning of the arc - * @param x2 {number} The x-coordinate of the end of the arc - * @param y2 {number} The y-coordinate of the end of the arc - * @param radius {number} The radius of the arc - * @return {Graphics} - */ -Graphics.prototype.arcTo = function (x1, y1, x2, y2, radius) -{ - if (this.currentPath) - { - if (this.currentPath.shape.points.length === 0) - { - this.currentPath.shape.points.push(x1, y1); - } - } - else - { - this.moveTo(x1, y1); - } - - var points = this.currentPath.shape.points, - fromX = points[points.length-2], - fromY = points[points.length-1], - a1 = fromY - y1, - b1 = fromX - x1, - a2 = y2 - y1, - b2 = x2 - x1, - mm = Math.abs(a1 * b2 - b1 * a2); - - if (mm < 1.0e-8 || radius === 0) - { - if (points[points.length-2] !== x1 || points[points.length-1] !== y1) - { - points.push(x1, y1); - } - } - else - { - var dd = a1 * a1 + b1 * b1, - cc = a2 * a2 + b2 * b2, - tt = a1 * a2 + b1 * b2, - k1 = radius * Math.sqrt(dd) / mm, - k2 = radius * Math.sqrt(cc) / mm, - j1 = k1 * tt / dd, - j2 = k2 * tt / cc, - cx = k1 * b2 + k2 * b1, - cy = k1 * a2 + k2 * a1, - px = b1 * (k2 + j1), - py = a1 * (k2 + j1), - qx = b2 * (k1 + j2), - qy = a2 * (k1 + j2), - startAngle = Math.atan2(py - cy, px - cx), - endAngle = Math.atan2(qy - cy, qx - cx); - - this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1); - } - - this.dirty = true; - - return this; -}; - -/** - * The arc method creates an arc/curve (used to create circles, or parts of circles). - * - * @param cx {number} The x-coordinate of the center of the circle - * @param cy {number} The y-coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @param startAngle {number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle) - * @param endAngle {number} The ending angle, in radians - * @param anticlockwise {boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {Graphics} - */ -Graphics.prototype.arc = function (cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - var points; - - // TODO - This if-else makes no sense. It uses currentPath in the else where it doesn't exist... - if (this.currentPath) - { - points = this.currentPath.shape.points; - - if (points.length === 0) - { - points.push(startX, startY); - } - else if (points[points.length-2] !== startX || points[points.length-1] !== startY) - { - points.push(startX, startY); - } - } - else - { - this.moveTo(startX, startY); - points = this.currentPath.shape.points; - } - - if (startAngle === endAngle) - { - return this; - } - - if (!anticlockwise && endAngle <= startAngle) - { - endAngle += Math.PI * 2; - } - else if (anticlockwise && startAngle <= endAngle) - { - startAngle += Math.PI * 2; - } - - var sweep = anticlockwise ? (startAngle - endAngle) *-1 : (endAngle - startAngle); - var segs = (Math.abs(sweep)/ (Math.PI * 2)) * 40; - - if (sweep === 0) - { - return this; - } - - var theta = sweep/(segs*2); - var theta2 = theta*2; - - var cTheta = Math.cos(theta); - var sTheta = Math.sin(theta); - - var segMinus = segs - 1; - - var remainder = ( segMinus % 1 ) / segMinus; - - for (var i = 0; i <= segMinus; ++i) - { - var real = i + remainder * i; - var angle = ((theta) + startAngle + (theta2 * real)); - - var c = Math.cos(angle); - var s = -Math.sin(angle); - - points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx, - ( (cTheta * -s) + (sTheta * c) ) * radius + cy); - } - - this.dirty = true; - - return this; -}; - -/** - * Specifies a simple one-color fill that subsequent calls to other Graphics methods - * (such as lineTo() or drawCircle()) use when drawing. - * - * @param color {number} the color of the fill - * @param alpha {number} the alpha of the fill - * @return {Graphics} - */ -Graphics.prototype.beginFill = function (color, alpha) -{ - this.filling = true; - this.fillColor = color || 0; - this.fillAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length <= 2) - { - this.currentPath.fill = this.filling; - this.currentPath.fillColor = this.fillColor; - this.currentPath.fillAlpha = this.fillAlpha; - } - } - return this; -}; - -/** - * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method. - * - * @return {Graphics} - */ -Graphics.prototype.endFill = function () -{ - this.filling = false; - this.fillColor = null; - this.fillAlpha = 1; - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @return {Graphics} - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {Graphics} - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {Graphics} - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {Array} The path data used to construct the polygon. - * @return {Graphics} - */ -Graphics.prototype.drawPolygon = function (path) -{ - if (!(path instanceof Array)) - { - path = Array.prototype.slice.call(arguments); - } - - this.drawShape(new math.Polygon(path)); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {Graphics} - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty = true; - this.clearDirty = true; - this.graphicsData = []; - - return this; -}; - -/** - * Useful function that returns a texture of the graphics object that can then be used to create sprites - * This can be quite useful if your geometry is complicated and needs to be reused multiple times. - * - * @param resolution {number} The resolution of the texture being generated - * @param scaleMode {number} Should be one of the scaleMode consts - * @return {Texture} a texture of the graphics object - */ -Graphics.prototype.generateTexture = function (resolution, scaleMode) -{ - resolution = resolution || 1; - - var bounds = this.getBounds(); - - var canvasBuffer = new CanvasBuffer(bounds.width * resolution, bounds.height * resolution); - - var texture = Texture.fromCanvas(canvasBuffer.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - canvasBuffer.context.scale(resolution, resolution); - - canvasBuffer.context.translate(-bounds.x,-bounds.y); - - CanvasGraphics.renderGraphics(this, canvasBuffer.context); - - return texture; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {WebGLRenderer} - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (this.isMask === true) - { - return; - } - - // this code may still be needed so leaving for now.. - // - /* - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture on the gpu too! - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.worldAlpha = this.worldAlpha; - - Sprite.prototype.renderWebGL.call(this._cachedSprite, renderer); - - return; - } - - */ - - if (this.glDirty) - { - this.dirty = true; - this.glDirty = false; - } - - renderer.setObjectRenderer(renderer.objectRenderers.graphics); - renderer.objectRenderers.graphics.render(this); - -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {CanvasRenderer} - * @private - */ -Graphics.prototype.renderCanvas = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.alpha <= 0 || this.isMask === true) - { - return; - } - - if (this._cacheAsBitmap) - { - if (this.dirty || this.cachedSpriteDirty) - { - this._generateCachedSprite(); - - // we will also need to update the texture - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.alpha = this.alpha; - - Sprite.prototype.renderCanvas.call(this._cachedSprite, renderer); - - return; - } - else - { - var context = renderer.context; - var transform = this.worldTransform; - - if (this.blendMode !== renderer.currentBlendMode) - { - renderer.currentBlendMode = this.blendMode; - context.globalCompositeOperation = renderer.blendModes[renderer.currentBlendMode]; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask, renderer); - } - - var resolution = renderer.resolution; - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - CanvasGraphics.renderGraphics(this, context); - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } - } -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @return {Rectangle} the rectangular bounding area - */ -Graphics.prototype.getBounds = function (matrix) -{ - // return an empty object if the item is a mask! - if (this.isMask) - { - return math.Rectangle.EMPTY; - } - - if (this.dirty) - { - this.updateLocalBounds(); - - this.glDirty = true; - this.cachedSpriteDirty = true; - this.dirty = false; - } - - var bounds = this._localBounds; - - var w0 = bounds.x; - var w1 = bounds.width + bounds.x; - - var h0 = bounds.y; - var h1 = bounds.height + bounds.y; - - var worldTransform = matrix || this.worldTransform; - - var a = worldTransform.a; - var b = worldTransform.b; - var c = worldTransform.c; - var d = worldTransform.d; - var tx = worldTransform.tx; - var ty = worldTransform.ty; - - var x1 = a * w1 + c * h1 + tx; - var y1 = d * h1 + b * w1 + ty; - - var x2 = a * w0 + c * h1 + tx; - var y2 = d * h1 + b * w0 + ty; - - var x3 = a * w0 + c * h0 + tx; - var y3 = d * h0 + b * w0 + ty; - - var x4 = a * w1 + c * h0 + tx; - var y4 = d * h0 + b * w1 + ty; - - var maxX = x1; - var maxY = y1; - - var minX = x1; - var minY = y1; - - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - this._bounds.x = minX; - this._bounds.width = maxX - minX; - - this._bounds.y = minY; - this._bounds.height = maxY - minY; - - return this._bounds; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.x = minX - padding; - this._localBounds.width = (maxX - minX) + padding * 2; - - this._localBounds.y = minY - padding; - this._localBounds.height = (maxY - minY) + padding * 2; -}; - -/** - * Generates the cached sprite when the sprite has cacheAsBitmap = true - * - * @private - */ -Graphics.prototype._generateCachedSprite = function () -{ - var bounds = this.getLocalBounds(); - - if (!this._cachedSprite) - { - var canvasBuffer = new CanvasBuffer(bounds.width, bounds.height); - var texture = Texture.fromCanvas(canvasBuffer.canvas); - - this._cachedSprite = new Sprite(texture); - this._cachedSprite.buffer = canvasBuffer; - - this._cachedSprite.worldTransform = this.worldTransform; - } - else - { - this._cachedSprite.buffer.resize(bounds.width, bounds.height); - } - - // leverage the anchor to account for the offset of the element - this._cachedSprite.anchor.x = -( bounds.x / bounds.width ); - this._cachedSprite.anchor.y = -( bounds.y / bounds.height ); - - // this._cachedSprite.buffer.context.save(); - this._cachedSprite.buffer.context.translate(-bounds.x,-bounds.y); - - // make sure we set the alpha of the graphics to 1 for the render.. - this.worldAlpha = 1; - - // now render the graphic.. - CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context); - - this._cachedSprite.alpha = this.alpha; -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Graphics.prototype.updateCachedSpriteTexture = function () -{ - var cachedSprite = this._cachedSprite; - var texture = cachedSprite.texture; - var canvas = cachedSprite.buffer.canvas; - - texture.baseTexture.width = canvas.width; - texture.baseTexture.height = canvas.height; - texture.crop.width = texture.frame.width = canvas.width; - texture.crop.height = texture.frame.height = canvas.height; - - cachedSprite._width = canvas.width; - cachedSprite._height = canvas.height; - - // update the dirty base textures - texture.baseTexture.dirty(); -}; - -/** - * Destroys a previous cached sprite. - * - */ -Graphics.prototype.destroyCachedSprite = function () -{ - this._cachedSprite.texture.destroy(true); - - // let the gc collect the unused sprite - // TODO could be object pooled! - this._cachedSprite = null; -}; - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw. - * @return {GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = this.filling; - this.currentPath = data; - } - - this.dirty = true; - - return data; -}; diff --git a/src/core/primitives/GraphicsData.js b/src/core/primitives/GraphicsData.js deleted file mode 100644 index 788f6f2..0000000 --- a/src/core/primitives/GraphicsData.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * A GraphicsData object. - * - * @class - * @namespace PIXI - */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - this.lineWidth = lineWidth; - this.lineColor = lineColor; - this.lineAlpha = lineAlpha; - this._lineTint = lineColor; - - this.fillColor = fillColor; - this.fillAlpha = fillAlpha; - this._fillTint = fillColor; - this.fill = fill; - - this.shape = shape; - this.type = shape.type; -} - -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {GraphicsData} - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; diff --git a/src/core/primitives/GraphicsRenderer.js b/src/core/primitives/GraphicsRenderer.js deleted file mode 100644 index 1a508f9..0000000 --- a/src/core/primitives/GraphicsRenderer.js +++ /dev/null @@ -1,874 +0,0 @@ -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; - -/** - * 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 ); - -// var matrix = graphics.worldTransform.clone(); -// var matrix = renderer.currentRenderTarget.projectionMatrix.clone(); -// matrix.append(graphics.worldTransform); - - for (var i = 0; i < webGL.data.length; i++) - { - if (webGL.data[i].mode === 1) - { - webGLData = webGL.data[i]; - - renderer.maskManager.pushStencil(graphics, webGLData, renderer); - - // render quad.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - renderer.maskManager.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.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, renderer.currentRenderTarget.projectionMatrix.toArray(true)); - - 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/primitives/WebGLGraphicsData.js b/src/core/primitives/WebGLGraphicsData.js deleted file mode 100755 index 41be102..0000000 --- a/src/core/primitives/WebGLGraphicsData.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * @class - * @private - */ -function WebGLGraphicsData(gl) { - this.gl = gl; - - //TODO does this need to be split before uploding?? - this.color = [0,0,0]; // color split! - this.points = []; - this.indices = []; - this.buffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - this.mode = 1; - this.alpha = 1; - this.dirty = true; -} - -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; -module.exports = WebGLGraphicsData; - -/** - * - */ -WebGLGraphicsData.prototype.reset = function () { - this.points = []; - this.indices = []; -}; - -/** - * - */ -WebGLGraphicsData.prototype.upload = function () { - var gl = this.gl; - -// this.lastIndex = graphics.graphicsData.length; - this.glPoints = new Float32Array(this.points); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - gl.bufferData(gl.ARRAY_BUFFER, this.glPoints, gl.STATIC_DRAW); - - this.glIndicies = new Uint16Array(this.indices); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.glIndicies, gl.STATIC_DRAW); - - this.dirty = false; -}; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index 6c1fb7d..4e82baa 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -174,6 +174,9 @@ // time to create the render managers! each one focuses on managing a state in webGL + // initialize the context so it is ready for the managers. + this._initContext(); + /** * Deals with managing the shader programs and their attribs * @member {WebGLShaderManager} @@ -204,17 +207,11 @@ */ this.blendModeManager = new WebGLBlendModeManager(this); - - this.blendModes = null; this._boundUpdateTexture = this.updateTexture.bind(this); this._boundDestroyTexture = this.destroyTexture.bind(this); - - - // time init the context.. - this._initContext(); this.currentRenderTarget = this.renderTarget; @@ -228,27 +225,18 @@ */ this._tempDisplayObjectParent = {worldTransform:new math.Matrix(), worldAlpha:1}; - /** - * Manages the renderer of specific objects. - * - * @member {object} - */ - this.objectRenderers = {}; - this.currentRenderer = new ObjectRenderer(); - - // create an instance of each registered object renderer - for (var o in WebGLRenderer._objectRenderers) { - this.objectRenderers[o] = new (WebGLRenderer._objectRenderers[o])(this); - } - + this.initPlugins(); } // constructor WebGLRenderer.prototype.constructor = WebGLRenderer; module.exports = WebGLRenderer; +WebGLRenderer.glContextId = 0; + +utils.pluginTarget.mixin(WebGLRenderer); utils.eventTarget.mixin(WebGLRenderer.prototype); Object.defineProperties(WebGLRenderer.prototype, { @@ -297,11 +285,9 @@ gl.enable(gl.BLEND); this.renderTarget = new RenderTarget(this.gl, this.width, this.height, null, true); - this.emit('context', gl); - // now resize and we are good to go! this.resize(this.width, this.height); }; @@ -349,7 +335,7 @@ gl.clear(gl.COLOR_BUFFER_BIT); } - this.renderDisplayObject(object, this.renderTarget)//this.projection); + this.renderDisplayObject(object, this.renderTarget);//this.projection); }; /** @@ -536,12 +522,7 @@ this.maskManager.destroy(); this.filterManager.destroy(); - for (var o in this.objectRenderers) { - this.objectRenderers[o].destroy(); - this.objectRenderers[o] = null; - } - - this.objectRenderers = null; + this.destroyPlugins(); // this.uuid = utils.uuid(); // this.type = CONST.WEBGL_RENDERER; @@ -611,11 +592,3 @@ this.blendModes[CONST.blendModes.LUMINOSITY] = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; } }; - -WebGLRenderer.glContextId = 0; -WebGLRenderer._objectRenderers = {}; - -WebGLRenderer.registerObjectRenderer = function (name, ctor) { - WebGLRenderer._objectRenderers[name] = ctor; -}; - diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js index 87c99f0..d6ed3c6 100644 --- a/src/core/renderers/webgl/managers/WebGLFilterManager.js +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -51,7 +51,7 @@ */ WebGLFilterManager.prototype.begin = function (buffer) { - this.defaultShader = this.renderer.shaderManager.defaultShader; + this.defaultShader = this.renderer.shaderManager.plugins.defaultShader; this.width = this.renderer.projection.x * 2; this.height = -this.renderer.projection.y * 2; @@ -385,7 +385,7 @@ if (!shader) { - shader = new Shader(gl); + shader = new Shader(this.renderer.shaderManager); shader.fragmentSrc = filter.fragmentSrc; shader.uniforms = filter.uniforms; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js index 6a1f782..d6a11c1 100644 --- a/src/core/renderers/webgl/managers/WebGLMaskManager.js +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -126,11 +126,11 @@ // bind the graphics object.. var projection = this.renderer.projection, offset = this.renderer.offset, - shader;// = this.renderer.shaderManager.primitiveShader; + shader;// = this.renderer.shaderManager.plugins.primitiveShader; if (webGLData.mode === 1) { - shader = this.renderer.shaderManager.complexPrimitiveShader; + shader = this.renderer.shaderManager.plugins.complexPrimitiveShader; this.renderer.shaderManager.setShader(shader); @@ -156,7 +156,7 @@ else { //this.renderer.shaderManager.activatePrimitiveShader(); - shader = this.renderer.shaderManager.primitiveShader; + shader = this.renderer.shaderManager.plugins.primitiveShader; this.renderer.shaderManager.setShader( shader ); gl.uniformMatrix3fv(shader.uniforms.translationMatrix._location, false, graphics.worldTransform.toArray(true)); @@ -292,11 +292,11 @@ */ WebGLMaskManager.prototype.pushMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); if (maskData.dirty) { - this.renderer.objectRenderers.graphics.updateGraphics(maskData, this.renderer.gl); + this.renderer.plugins.graphics.updateGraphics(maskData, this.renderer.gl); } if (!maskData._webGL[this.renderer.gl.id].data.length) @@ -314,7 +314,7 @@ */ WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.setObjectRenderer(this.renderer.objectRenderers.graphics); + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); this.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); }; diff --git a/src/core/renderers/webgl/managers/WebGLShaderManager.js b/src/core/renderers/webgl/managers/WebGLShaderManager.js index 90c6477..9ebb58e 100644 --- a/src/core/renderers/webgl/managers/WebGLShaderManager.js +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -1,9 +1,5 @@ var WebGLManager = require('./WebGLManager'), - PrimitiveShader = require('../shaders/PrimitiveShader'), - ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), - Shader = require('../shaders/Shader'), - FastShader = require('../shaders/FastShader'), - StripShader = require('../shaders/StripShader'); + utils = require('../../../utils'); /** * @class @@ -51,41 +47,16 @@ */ this.currentShader = null; - // this shader is used for rendering primitives - this.primitiveShader = null; - - // this shader is used for rendering triangle strips - this.complexPrimitiveShader = null; - - // this shader is used for the default sprite rendering - this.defaultShader = null; - - // this shader is used for the fast sprite rendering - this.fastShader = null; - - // the next one is used for rendering triangle strips - this.stripShader = null; + this.initPlugins(); // listen for context and update necessary shaders var self = this; - this.renderer.on('context', function (event) + this.renderer.on('context', function () { - var gl = event.data; - - // this shader is used for rendering primitives - self.primitiveShader = new PrimitiveShader(gl); - - // this shader is used for rendering triangle strips - self.complexPrimitiveShader = new ComplexPrimitiveShader(gl); - - // this shader is used for the default sprite rendering - self.defaultShader = new Shader(gl); - - // this shader is used for the fast sprite rendering - self.fastShader = new FastShader(gl); - - // the next one is used for rendering triangle strips - self.stripShader = new StripShader(gl); + for (var o in this.plugins) + { + this.plugins[o] = new (this.plugins[o].constructor)(self); + } self.setShader(self.defaultShader); }); @@ -95,6 +66,8 @@ WebGLShaderManager.prototype.constructor = WebGLShaderManager; module.exports = WebGLShaderManager; +utils.pluginTarget.mixin(WebGLShaderManager); + /** * Takes the attributes given in parameters. * @@ -164,24 +137,11 @@ */ WebGLShaderManager.prototype.destroy = function () { + this.destroyPlugins(); + this.attribState = null; this.tempAttribState = null; - this.primitiveShader.destroy(); - this.primitiveShader = null; - - this.complexPrimitiveShader.destroy(); - this.complexPrimitiveShader = null; - - this.defaultShader.destroy(); - this.defaultShader = null; - - this.fastShader.destroy(); - this.fastShader = null; - - this.stripShader.destroy(); - this.stripShader = null; - this.renderer = null; }; diff --git a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js b/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js deleted file mode 100644 index f8a07da..0000000 --- a/src/core/renderers/webgl/shaders/ComplexPrimitiveShader.js +++ /dev/null @@ -1,53 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function ComplexPrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform vec3 tint;', - 'uniform float alpha;', - 'uniform vec3 color;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = vec4(color * alpha * tint, alpha);',//" * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - color: { type: '3f', value: [0,0,0] }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -ComplexPrimitiveShader.prototype = Object.create(Shader.prototype); -ComplexPrimitiveShader.prototype.constructor = ComplexPrimitiveShader; -module.exports = ComplexPrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/FastShader.js b/src/core/renderers/webgl/shaders/FastShader.js index d198f10..c80d040 100644 --- a/src/core/renderers/webgl/shaders/FastShader.js +++ b/src/core/renderers/webgl/shaders/FastShader.js @@ -1,15 +1,16 @@ -var Shader = require('./Shader'); +var Shader = require('./Shader'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @extends Shader * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. */ -function FastShader(gl) +function FastShader(shaderManager) { Shader.call(this, - gl, + shaderManager, // vertex shader [ 'attribute vec2 aVertexPosition;', @@ -70,3 +71,5 @@ FastShader.prototype = Object.create(Shader.prototype); FastShader.prototype.constructor = FastShader; module.exports = FastShader; + +WebGLShaderManager.registerPlugin('fastShader', FastShader); diff --git a/src/core/renderers/webgl/shaders/PrimitiveShader.js b/src/core/renderers/webgl/shaders/PrimitiveShader.js deleted file mode 100644 index 753c475..0000000 --- a/src/core/renderers/webgl/shaders/PrimitiveShader.js +++ /dev/null @@ -1,55 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', - - 'uniform float alpha;', - 'uniform float flipY;', - 'uniform vec3 tint;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'varying vec4 vColor;', - - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aColor:0 - } - ); -} - -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; -module.exports = PrimitiveShader; diff --git a/src/core/renderers/webgl/shaders/Shader.js b/src/core/renderers/webgl/shaders/Shader.js index d065a76..c60c5f9 100644 --- a/src/core/renderers/webgl/shaders/Shader.js +++ b/src/core/renderers/webgl/shaders/Shader.js @@ -1,12 +1,16 @@ -var utils = require('../../../utils'); +var utils = require('../../../utils'), + WebGLShaderManager = require('../managers/WebGLShaderManager'); /** * @class * @namespace PIXI - * @param [fragmentSrc] {string} The source of the fragment shader. + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. * @param [vertexSrc] {string} The source of the vertex shader. + * @param [fragmentSrc] {string} The source of the fragment shader. + * @param customUniforms {object} Custom uniforms to use to augment the built-in ones. + * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Shader(gl, vertexSrc, fragmentSrc, customUniforms, customAttributes) +function Shader(shaderManager, vertexSrc, fragmentSrc, customUniforms, customAttributes) { /** * @member {number} @@ -18,7 +22,7 @@ * @member {WebGLContext} * @readonly */ - this.gl = gl; + this.gl = shaderManager.renderer.gl; /** * The WebGL program. @@ -31,8 +35,8 @@ uSampler: { type: 'sampler2D', value: 0 }, projectionVector: { type: '2f', value: { x: 0, y: 0 } }, offsetVector: { type: '2f', value: { x: 0, y: 0 } }, - projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, - 0, 1, 0, + projectionMatrix: { type: 'mat3', value: new Float32Array(1, 0, 0, + 0, 1, 0, 0, 0, 1) }, dimensions: { type: '4f', value: new Float32Array(4) } }; @@ -103,6 +107,8 @@ Shader.prototype.constructor = Shader; module.exports = Shader; +WebGLShaderManager.registerPlugin('defaultShader', Shader); + Shader.prototype.init = function () { this.compile(); @@ -506,4 +512,4 @@ } return shader; -}; \ No newline at end of file +}; diff --git a/src/core/renderers/webgl/shaders/SpriteShader.js b/src/core/renderers/webgl/shaders/SpriteShader.js deleted file mode 100644 index 1805b0a..0000000 --- a/src/core/renderers/webgl/shaders/SpriteShader.js +++ /dev/null @@ -1,42 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function SpriteShader(gl) -{ - Shader.call(this, - gl, - null, - // fragment shader - [ - 'precision lowp float;', - - 'varying vec2 vTextureCoord;', - 'varying vec4 vColor;', - - 'uniform sampler2D uSampler;', - - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', - '}' - ].join('\n'), - // custom uniforms - { - tint: { type: '3f', value: [0, 0, 0] }, - flipY: { type: '1f', value: 0 }, - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - }, - { - aTextureCoord: 0, - aColor: 0 - } - ); -} - -SpriteShader.prototype = Object.create(Shader.prototype); -SpriteShader.prototype.constructor = SpriteShader; -module.exports = SpriteShader; diff --git a/src/core/renderers/webgl/shaders/StripShader.js b/src/core/renderers/webgl/shaders/StripShader.js deleted file mode 100644 index f487381..0000000 --- a/src/core/renderers/webgl/shaders/StripShader.js +++ /dev/null @@ -1,54 +0,0 @@ -var Shader = require('./Shader'); - -/** - * @class - * @namespace PIXI - * @param gl {WebGLContext} the current WebGL drawing context - */ -function StripShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - // 'attribute vec4 aColor;', - - 'uniform mat3 translationMatrix;', - 'uniform vec2 projectionVector;', - 'uniform vec2 offsetVector;', - - 'varying vec2 vTextureCoord;', - - 'void main(void){', - ' vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);', - ' v -= offsetVector.xyx;', - ' gl_Position = vec4( v.x / projectionVector.x -1.0, v.y / -projectionVector.y + 1.0 , 0.0, 1.0);', - ' vTextureCoord = aTextureCoord;', - '}' - ].join('\n'), - // fragment shader - [ - 'precision mediump float;', - - 'uniform float alpha;', - 'uniform sampler2D uSampler;', - - 'varying vec2 vTextureCoord;', - - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y)) * alpha;', - '}' - ].join('\n'), - // custom uniforms - { - alpha: { type: '1f', value: 0 }, - translationMatrix: { type: 'mat3', value: new Float32Array(9) } - } - ); -} - -StripShader.prototype = Object.create(Shader.prototype); -StripShader.prototype.constructor = StripShader; -module.exports = StripShader; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index 0a34e55..43e2d00 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -33,7 +33,7 @@ * @property texture * @type Any */ - this.texture = null + this.texture = null; this.width = 0; this.height = 0; @@ -56,11 +56,11 @@ this.frameBuffer = gl.createFramebuffer(); - /* + /* A frame buffer needs a target to render to.. create a texture and bind it attach it to the framebuffer.. */ - + this.texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, this.texture); @@ -70,13 +70,13 @@ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, scaleMode === CONST.scaleModes.LINEAR ? gl.LINEAR : gl.NEAREST); 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); - + gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer ); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0); - - /* + + /* The stencil buffer is used for masking in pixi lets create one and then add attach it to the framebuffer.. */ @@ -86,7 +86,7 @@ } this.resize(width, height); - + }; RenderTarget.prototype.constructor = RenderTarget; @@ -94,21 +94,21 @@ /** * Clears the filter texture. -* +* * @method clear */ RenderTarget.prototype.clear = function() { var gl = this.gl; - + gl.clearColor(0,0,0, 0); gl.clear(gl.COLOR_BUFFER_BIT); }; RenderTarget.prototype.activate = function() { - this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer ); -} + this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer); +}; /** * Resizes the texture to the specified width and height @@ -119,25 +119,25 @@ */ RenderTarget.prototype.resize = function(width, height) { - width = width | 0; - height = height | 0; - - if(this.width === width && this.height === height) return; + if (this.width === width && this.height === height) { + return; + } this.width = width; this.height = height; + this.projectionMatrix = new math.Matrix(); - - if(!this.root) + + if (!this.root) { var gl = this.gl; this.projectionMatrix.a = 1/width*2; - this.projectionMatrix.d = 1/height*2; + this.projectionMatrix.d = -1/height*2; this.projectionMatrix.tx = -1; - this.projectionMatrix.ty = -1; + this.projectionMatrix.ty = 1; gl.bindTexture(gl.TEXTURE_2D, this.texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width , height , 0, gl.RGBA, gl.UNSIGNED_BYTE, null); @@ -157,7 +157,7 @@ /** * Destroys the filter texture. -* +* * @method destroy */ RenderTarget.prototype.destroy = function() diff --git a/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js b/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js index a63f4d7..3f37b7d 100644 --- a/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js +++ b/src/core/renderers/webgl/utils/WebGLFastSpriteBatch.js @@ -183,7 +183,7 @@ */ WebGLFastSpriteBatch.prototype.begin = function (spriteBatch) { - this.shader = this.renderer.shaderManager.fastShader; + this.shader = this.renderer.shaderManager.plugins.fastShader; this.matrix = spriteBatch.worldTransform.toArray(true); diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index bf8db28..2dc47ff 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -201,8 +201,8 @@ Sprite.prototype._renderWebGL = function (renderer) { - renderer.setObjectRenderer(renderer.objectRenderers.sprite); - renderer.objectRenderers.sprite.render(this); + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); }; /** diff --git a/src/core/sprites/SpriteBatch.js b/src/core/sprites/SpriteBatch.js index 63cebc0..4fe33e4 100644 --- a/src/core/sprites/SpriteBatch.js +++ b/src/core/sprites/SpriteBatch.js @@ -61,7 +61,7 @@ renderer.spriteBatch.stop(); - renderer.shaderManager.setShader(renderer.shaderManager.fastShader); + renderer.shaderManager.setShader(renderer.shaderManager.plugins.fastShader); renderer.fastSpriteBatch.begin(this); renderer.fastSpriteBatch.render(this); diff --git a/src/core/sprites/SpriteRenderer.js b/src/core/sprites/SpriteRenderer.js deleted file mode 100644 index fd9a802..0000000 --- a/src/core/sprites/SpriteRenderer.js +++ /dev/null @@ -1,537 +0,0 @@ -var ObjectRenderer = require('../renderers/webgl/utils/ObjectRenderer'), - Shader = require('../renderers/webgl/shaders/Shader'), - SpriteShader = require('../renderers/webgl/shaders/SpriteShader'), - WebGLRenderer = require('../renderers/webgl/WebGLRenderer'); - math = require('../math'); - -/** - * @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.setupContext(); -} - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; -module.exports = 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 = sprite; - 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 - gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, this.renderer.currentRenderTarget.projectionMatrix.toArray(true)); - } - } - - 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; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js new file mode 100644 index 0000000..c006ba4 --- /dev/null +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -0,0 +1,533 @@ +var ObjectRenderer = require('../../renderers/webgl/utils/ObjectRenderer'), + Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLRenderer = require('../../renderers/webgl/WebGLRenderer'), + SpriteShader = require('./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) +{ + 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; + + this.setupContext(); +} + +SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); +SpriteRenderer.prototype.constructor = SpriteRenderer; +module.exports = SpriteRenderer; + +WebGLRenderer.registerPlugin('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(this.renderer.shaderManager); + + // 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(this.renderer.shaderManager, 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 + gl.uniformMatrix3fv(shader.uniforms.projectionMatrix._location, false, this.renderer.currentRenderTarget.projectionMatrix.toArray(true)); + } + } + + 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; +}; diff --git a/src/core/sprites/webgl/SpriteShader.js b/src/core/sprites/webgl/SpriteShader.js new file mode 100644 index 0000000..3925e9f --- /dev/null +++ b/src/core/sprites/webgl/SpriteShader.js @@ -0,0 +1,45 @@ +var Shader = require('../../renderers/webgl/shaders/Shader'), + WebGLShaderManager = require('../../renderers/webgl/managers/WebGLShaderManager'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function SpriteShader(shaderManager) +{ + Shader.call(this, + shaderManager, + null, + // fragment shader + [ + 'precision lowp float;', + + 'varying vec2 vTextureCoord;', + 'varying vec4 vColor;', + + 'uniform sampler2D uSampler;', + + 'void main(void){', + ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vColor ;', + '}' + ].join('\n'), + // custom uniforms + { + tint: { type: '3f', value: [0, 0, 0] }, + flipY: { type: '1f', value: 0 }, + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + }, + { + aTextureCoord: 0, + aColor: 0 + } + ); +} + +SpriteShader.prototype = Object.create(Shader.prototype); +SpriteShader.prototype.constructor = SpriteShader; +module.exports = SpriteShader; + +WebGLShaderManager.registerPlugin('fastShader', SpriteShader); diff --git a/src/core/utils/index.js b/src/core/utils/index.js index bfae7ae..1d451c0 100644 --- a/src/core/utils/index.js +++ b/src/core/utils/index.js @@ -7,9 +7,10 @@ _uid: 0, _saidHello: false, - PolyK: require('./PolyK'), - EventData: require('./EventData'), - eventTarget: require('./eventTarget'), + PolyK: require('./PolyK'), + EventData: require('./EventData'), + eventTarget: require('./eventTarget'), + pluginTarget: require('./pluginTarget'), /** * Gets the next uuid diff --git a/src/core/utils/pluginTarget.js b/src/core/utils/pluginTarget.js new file mode 100644 index 0000000..885fe0e --- /dev/null +++ b/src/core/utils/pluginTarget.js @@ -0,0 +1,37 @@ +/** + * Mixins functionality to make an object have "plugins". + * + * @mixin + * @namespace PIXI + * @param obj {object} The object to mix into. + * @example + * function MyObject() {} + * + * pluginTarget.mixin(MyObject); + */ +function pluginTarget(obj) { + obj.__plugins = {}; + + obj.registerPlugin = function (pluginName, ctor) { + obj.__plugins[pluginName] = ctor; + }; + + obj.prototype.initPlugins = function () { + this.plugins = {}; + + for (var o in obj.__plugins) { + this.plugins[o] = new (obj.__plugins[o])(this); + } + }; + + obj.prototype.destroyPlugins = function () { + for (var o in this.plugins) { + this.plugins[o].destroy(); + this.plugins[o] = null; + } + + this.plugins = null; + }; +} + +module.exports = pluginTarget; diff --git a/src/extras/Strip.js b/src/extras/Strip.js index 57691b5..6bf78cb 100644 --- a/src/extras/Strip.js +++ b/src/extras/Strip.js @@ -89,7 +89,7 @@ this._initWebGL(renderer); } - renderer.shaderManager.setShader(renderer.shaderManager.stripShader); + renderer.shaderManager.setShader(renderer.shaderManager.plugins.stripShader); this._renderStrip(renderer); @@ -128,7 +128,7 @@ var gl = renderer.gl; var projection = renderer.projection, offset = renderer.offset, - shader = renderer.shaderManager.stripShader; + shader = renderer.shaderManager.plugins.stripShader; var drawMode = this.drawMode === Strip.DrawModes.TRIANGLE_STRIP ? gl.TRIANGLE_STRIP : gl.TRIANGLES; diff --git a/src/extras/StripShader.js b/src/extras/StripShader.js new file mode 100644 index 0000000..3e7c47f --- /dev/null +++ b/src/extras/StripShader.js @@ -0,0 +1,56 @@ +var core = require('../core'); + +/** + * @class + * @namespace PIXI + * @param shaderManager {WebGLShaderManager} The webgl shader manager this shader works for. + */ +function StripShader(shaderManager) +{ + core.Shader.call(this, + shaderManager, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + // 'attribute vec4 aColor;', + + 'uniform mat3 translationMatrix;', + 'uniform vec2 projectionVector;', + 'uniform vec2 offsetVector;', + + 'varying vec2 vTextureCoord;', + + 'void main(void){', + ' vec3 v = translationMatrix * vec3(aVertexPosition , 1.0);', + ' v -= offsetVector.xyx;', + ' gl_Position = vec4( v.x / projectionVector.x -1.0, v.y / -projectionVector.y + 1.0 , 0.0, 1.0);', + ' vTextureCoord = aTextureCoord;', + '}' + ].join('\n'), + // fragment shader + [ + 'precision mediump float;', + + 'uniform float alpha;', + 'uniform sampler2D uSampler;', + + 'varying vec2 vTextureCoord;', + + 'void main(void){', + ' gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x, vTextureCoord.y)) * alpha;', + '}' + ].join('\n'), + // custom uniforms + { + alpha: { type: '1f', value: 0 }, + translationMatrix: { type: 'mat3', value: new Float32Array(9) } + } + ); +} + +StripShader.prototype = Object.create(core.Shader.prototype); +StripShader.prototype.constructor = StripShader; +module.exports = StripShader; + +core.WebGLShaderManager.registerPlugin('stripShader', StripShader); diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index 61d5d54..131c6b7 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -181,9 +181,9 @@ texture._frame.width = this.width; texture._frame.height = this.height; - renderer.setObjectRenderer(renderer.objectRenderers.sprite); - renderer.objectRenderers.sprite.render(this); - + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + texture._uvs = tempUvs; texture._frame.width = tempWidth; texture._frame.height = tempHeight; diff --git a/src/extras/index.js b/src/extras/index.js index 7eea136..f80b7ba 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -12,5 +12,6 @@ MovieClip: require('./MovieClip'), Rope: require('./Rope'), Strip: require('./Strip'), + StripShader: require('./StripShader'), TilingSprite: require('./TilingSprite') };