diff --git a/src/core/const.js b/src/core/const.js index 2803e5b..aea72ec 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -130,5 +130,25 @@ backgroundColor: 0x000000, clearBeforeRender: true, preserveDrawingBuffer: false + }, + + /** + * Constants that identify shapes, mainly to prevent `instanceof` calls. + * + * @static + * @constant + * @property {object} SHAPES + * @property {object} SHAPES.POLY=0 + * @property {object} SHAPES.RECT=1 + * @property {object} SHAPES.CIRC=2 + * @property {object} SHAPES.ELIP=3 + * @property {object} SHAPES.RREC=4 + */ + SHAPES: { + POLY = 0; + RECT = 1; + CIRC = 2; + ELIP = 3; + RREC = 4; } }; diff --git a/src/core/const.js b/src/core/const.js index 2803e5b..aea72ec 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -130,5 +130,25 @@ backgroundColor: 0x000000, clearBeforeRender: true, preserveDrawingBuffer: false + }, + + /** + * Constants that identify shapes, mainly to prevent `instanceof` calls. + * + * @static + * @constant + * @property {object} SHAPES + * @property {object} SHAPES.POLY=0 + * @property {object} SHAPES.RECT=1 + * @property {object} SHAPES.CIRC=2 + * @property {object} SHAPES.ELIP=3 + * @property {object} SHAPES.RREC=4 + */ + SHAPES: { + POLY = 0; + RECT = 1; + CIRC = 2; + ELIP = 3; + RREC = 4; } }; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index bfd59cf..7c0e7c6 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -95,7 +95,7 @@ this.filterArea = null; /** - * cached sin rotation and cos rotation + * cached sin rotation * * @member {number} * @private @@ -103,7 +103,7 @@ this._sr = 0; /** - * cached sin rotation and cos rotation + * cached cos rotation * * @member {number} * @private @@ -400,7 +400,7 @@ DisplayObject.prototype.generateTexture = function (resolution, scaleMode, renderer) { var bounds = this.getLocalBounds(); - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); _tempMatrix.tx = -bounds.x; _tempMatrix.ty = -bounds.y; @@ -474,7 +474,7 @@ var bounds = this.getLocalBounds(); if (!this._cachedSprite) { - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0); //, renderer); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0); this._cachedSprite = new Sprite(renderTexture); this._cachedSprite.worldTransform = this.worldTransform; diff --git a/src/core/const.js b/src/core/const.js index 2803e5b..aea72ec 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -130,5 +130,25 @@ backgroundColor: 0x000000, clearBeforeRender: true, preserveDrawingBuffer: false + }, + + /** + * Constants that identify shapes, mainly to prevent `instanceof` calls. + * + * @static + * @constant + * @property {object} SHAPES + * @property {object} SHAPES.POLY=0 + * @property {object} SHAPES.RECT=1 + * @property {object} SHAPES.CIRC=2 + * @property {object} SHAPES.ELIP=3 + * @property {object} SHAPES.RREC=4 + */ + SHAPES: { + POLY = 0; + RECT = 1; + CIRC = 2; + ELIP = 3; + RREC = 4; } }; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index bfd59cf..7c0e7c6 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -95,7 +95,7 @@ this.filterArea = null; /** - * cached sin rotation and cos rotation + * cached sin rotation * * @member {number} * @private @@ -103,7 +103,7 @@ this._sr = 0; /** - * cached sin rotation and cos rotation + * cached cos rotation * * @member {number} * @private @@ -400,7 +400,7 @@ DisplayObject.prototype.generateTexture = function (resolution, scaleMode, renderer) { var bounds = this.getLocalBounds(); - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); _tempMatrix.tx = -bounds.x; _tempMatrix.ty = -bounds.y; @@ -474,7 +474,7 @@ var bounds = this.getLocalBounds(); if (!this._cachedSprite) { - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0); //, renderer); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0); this._cachedSprite = new Sprite(renderTexture); this._cachedSprite.worldTransform = this.worldTransform; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 3f6a3b6..69cfdc9 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Circle object can be used to specify a hit area for displayObjects @@ -29,10 +30,11 @@ this.radius = radius || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.CIRC in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.CIRC; } Circle.prototype.constructor = Circle; diff --git a/src/core/const.js b/src/core/const.js index 2803e5b..aea72ec 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -130,5 +130,25 @@ backgroundColor: 0x000000, clearBeforeRender: true, preserveDrawingBuffer: false + }, + + /** + * Constants that identify shapes, mainly to prevent `instanceof` calls. + * + * @static + * @constant + * @property {object} SHAPES + * @property {object} SHAPES.POLY=0 + * @property {object} SHAPES.RECT=1 + * @property {object} SHAPES.CIRC=2 + * @property {object} SHAPES.ELIP=3 + * @property {object} SHAPES.RREC=4 + */ + SHAPES: { + POLY = 0; + RECT = 1; + CIRC = 2; + ELIP = 3; + RREC = 4; } }; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index bfd59cf..7c0e7c6 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -95,7 +95,7 @@ this.filterArea = null; /** - * cached sin rotation and cos rotation + * cached sin rotation * * @member {number} * @private @@ -103,7 +103,7 @@ this._sr = 0; /** - * cached sin rotation and cos rotation + * cached cos rotation * * @member {number} * @private @@ -400,7 +400,7 @@ DisplayObject.prototype.generateTexture = function (resolution, scaleMode, renderer) { var bounds = this.getLocalBounds(); - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); _tempMatrix.tx = -bounds.x; _tempMatrix.ty = -bounds.y; @@ -474,7 +474,7 @@ var bounds = this.getLocalBounds(); if (!this._cachedSprite) { - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0); //, renderer); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0); this._cachedSprite = new Sprite(renderTexture); this._cachedSprite.worldTransform = this.worldTransform; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 3f6a3b6..69cfdc9 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Circle object can be used to specify a hit area for displayObjects @@ -29,10 +30,11 @@ this.radius = radius || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.CIRC in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.CIRC; } Circle.prototype.constructor = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index fc9df4d..4f2d066 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Ellipse object can be used to specify a hit area for displayObjects @@ -36,10 +37,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.ELIP in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.ELIP; } Ellipse.prototype.constructor = Ellipse; diff --git a/src/core/const.js b/src/core/const.js index 2803e5b..aea72ec 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -130,5 +130,25 @@ backgroundColor: 0x000000, clearBeforeRender: true, preserveDrawingBuffer: false + }, + + /** + * Constants that identify shapes, mainly to prevent `instanceof` calls. + * + * @static + * @constant + * @property {object} SHAPES + * @property {object} SHAPES.POLY=0 + * @property {object} SHAPES.RECT=1 + * @property {object} SHAPES.CIRC=2 + * @property {object} SHAPES.ELIP=3 + * @property {object} SHAPES.RREC=4 + */ + SHAPES: { + POLY = 0; + RECT = 1; + CIRC = 2; + ELIP = 3; + RREC = 4; } }; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index bfd59cf..7c0e7c6 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -95,7 +95,7 @@ this.filterArea = null; /** - * cached sin rotation and cos rotation + * cached sin rotation * * @member {number} * @private @@ -103,7 +103,7 @@ this._sr = 0; /** - * cached sin rotation and cos rotation + * cached cos rotation * * @member {number} * @private @@ -400,7 +400,7 @@ DisplayObject.prototype.generateTexture = function (resolution, scaleMode, renderer) { var bounds = this.getLocalBounds(); - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); _tempMatrix.tx = -bounds.x; _tempMatrix.ty = -bounds.y; @@ -474,7 +474,7 @@ var bounds = this.getLocalBounds(); if (!this._cachedSprite) { - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0); //, renderer); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0); this._cachedSprite = new Sprite(renderTexture); this._cachedSprite.worldTransform = this.worldTransform; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 3f6a3b6..69cfdc9 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Circle object can be used to specify a hit area for displayObjects @@ -29,10 +30,11 @@ this.radius = radius || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.CIRC in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.CIRC; } Circle.prototype.constructor = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index fc9df4d..4f2d066 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Ellipse object can be used to specify a hit area for displayObjects @@ -36,10 +37,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.ELIP in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.ELIP; } Ellipse.prototype.constructor = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index 5d4a464..ae89148 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -1,4 +1,5 @@ -var Point = require('../Point'); +var Point = require('../Point'), + CONST = require('../../const'); /** * @class @@ -33,6 +34,13 @@ * @member {Point[]} */ this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + */ + this.type = CONST.SHAPES.POLY; } Polygon.prototype.constructor = Polygon; diff --git a/src/core/const.js b/src/core/const.js index 2803e5b..aea72ec 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -130,5 +130,25 @@ backgroundColor: 0x000000, clearBeforeRender: true, preserveDrawingBuffer: false + }, + + /** + * Constants that identify shapes, mainly to prevent `instanceof` calls. + * + * @static + * @constant + * @property {object} SHAPES + * @property {object} SHAPES.POLY=0 + * @property {object} SHAPES.RECT=1 + * @property {object} SHAPES.CIRC=2 + * @property {object} SHAPES.ELIP=3 + * @property {object} SHAPES.RREC=4 + */ + SHAPES: { + POLY = 0; + RECT = 1; + CIRC = 2; + ELIP = 3; + RREC = 4; } }; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index bfd59cf..7c0e7c6 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -95,7 +95,7 @@ this.filterArea = null; /** - * cached sin rotation and cos rotation + * cached sin rotation * * @member {number} * @private @@ -103,7 +103,7 @@ this._sr = 0; /** - * cached sin rotation and cos rotation + * cached cos rotation * * @member {number} * @private @@ -400,7 +400,7 @@ DisplayObject.prototype.generateTexture = function (resolution, scaleMode, renderer) { var bounds = this.getLocalBounds(); - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); _tempMatrix.tx = -bounds.x; _tempMatrix.ty = -bounds.y; @@ -474,7 +474,7 @@ var bounds = this.getLocalBounds(); if (!this._cachedSprite) { - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0); //, renderer); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0); this._cachedSprite = new Sprite(renderTexture); this._cachedSprite.worldTransform = this.worldTransform; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 3f6a3b6..69cfdc9 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Circle object can be used to specify a hit area for displayObjects @@ -29,10 +30,11 @@ this.radius = radius || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.CIRC in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.CIRC; } Circle.prototype.constructor = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index fc9df4d..4f2d066 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Ellipse object can be used to specify a hit area for displayObjects @@ -36,10 +37,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.ELIP in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.ELIP; } Ellipse.prototype.constructor = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index 5d4a464..ae89148 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -1,4 +1,5 @@ -var Point = require('../Point'); +var Point = require('../Point'), + CONST = require('../../const'); /** * @class @@ -33,6 +34,13 @@ * @member {Point[]} */ this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + */ + this.type = CONST.SHAPES.POLY; } Polygon.prototype.constructor = Polygon; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index c3454b5..16ac62a 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * the Rectangle object is an area defined by its position, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -34,10 +36,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RECT; } Rectangle.prototype.constructor = Rectangle; diff --git a/src/core/const.js b/src/core/const.js index 2803e5b..aea72ec 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -130,5 +130,25 @@ backgroundColor: 0x000000, clearBeforeRender: true, preserveDrawingBuffer: false + }, + + /** + * Constants that identify shapes, mainly to prevent `instanceof` calls. + * + * @static + * @constant + * @property {object} SHAPES + * @property {object} SHAPES.POLY=0 + * @property {object} SHAPES.RECT=1 + * @property {object} SHAPES.CIRC=2 + * @property {object} SHAPES.ELIP=3 + * @property {object} SHAPES.RREC=4 + */ + SHAPES: { + POLY = 0; + RECT = 1; + CIRC = 2; + ELIP = 3; + RREC = 4; } }; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index bfd59cf..7c0e7c6 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -95,7 +95,7 @@ this.filterArea = null; /** - * cached sin rotation and cos rotation + * cached sin rotation * * @member {number} * @private @@ -103,7 +103,7 @@ this._sr = 0; /** - * cached sin rotation and cos rotation + * cached cos rotation * * @member {number} * @private @@ -400,7 +400,7 @@ DisplayObject.prototype.generateTexture = function (resolution, scaleMode, renderer) { var bounds = this.getLocalBounds(); - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); _tempMatrix.tx = -bounds.x; _tempMatrix.ty = -bounds.y; @@ -474,7 +474,7 @@ var bounds = this.getLocalBounds(); if (!this._cachedSprite) { - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0); //, renderer); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0); this._cachedSprite = new Sprite(renderTexture); this._cachedSprite.worldTransform = this.worldTransform; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 3f6a3b6..69cfdc9 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Circle object can be used to specify a hit area for displayObjects @@ -29,10 +30,11 @@ this.radius = radius || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.CIRC in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.CIRC; } Circle.prototype.constructor = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index fc9df4d..4f2d066 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Ellipse object can be used to specify a hit area for displayObjects @@ -36,10 +37,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.ELIP in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.ELIP; } Ellipse.prototype.constructor = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index 5d4a464..ae89148 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -1,4 +1,5 @@ -var Point = require('../Point'); +var Point = require('../Point'), + CONST = require('../../const'); /** * @class @@ -33,6 +34,13 @@ * @member {Point[]} */ this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + */ + this.type = CONST.SHAPES.POLY; } Polygon.prototype.constructor = Polygon; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index c3454b5..16ac62a 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * the Rectangle object is an area defined by its position, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -34,10 +36,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RECT; } Rectangle.prototype.constructor = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0f2abfc..27d286e 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * The Rounded Rectangle object is an area defined by its position and has nice rounded corners, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -41,10 +43,11 @@ this.radius = radius || 20; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RRECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RREC; } RoundedRectangle.prototype.constructor = RoundedRectangle; diff --git a/src/core/const.js b/src/core/const.js index 2803e5b..aea72ec 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -130,5 +130,25 @@ backgroundColor: 0x000000, clearBeforeRender: true, preserveDrawingBuffer: false + }, + + /** + * Constants that identify shapes, mainly to prevent `instanceof` calls. + * + * @static + * @constant + * @property {object} SHAPES + * @property {object} SHAPES.POLY=0 + * @property {object} SHAPES.RECT=1 + * @property {object} SHAPES.CIRC=2 + * @property {object} SHAPES.ELIP=3 + * @property {object} SHAPES.RREC=4 + */ + SHAPES: { + POLY = 0; + RECT = 1; + CIRC = 2; + ELIP = 3; + RREC = 4; } }; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index bfd59cf..7c0e7c6 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -95,7 +95,7 @@ this.filterArea = null; /** - * cached sin rotation and cos rotation + * cached sin rotation * * @member {number} * @private @@ -103,7 +103,7 @@ this._sr = 0; /** - * cached sin rotation and cos rotation + * cached cos rotation * * @member {number} * @private @@ -400,7 +400,7 @@ DisplayObject.prototype.generateTexture = function (resolution, scaleMode, renderer) { var bounds = this.getLocalBounds(); - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); _tempMatrix.tx = -bounds.x; _tempMatrix.ty = -bounds.y; @@ -474,7 +474,7 @@ var bounds = this.getLocalBounds(); if (!this._cachedSprite) { - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0); //, renderer); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0); this._cachedSprite = new Sprite(renderTexture); this._cachedSprite.worldTransform = this.worldTransform; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 3f6a3b6..69cfdc9 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Circle object can be used to specify a hit area for displayObjects @@ -29,10 +30,11 @@ this.radius = radius || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.CIRC in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.CIRC; } Circle.prototype.constructor = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index fc9df4d..4f2d066 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Ellipse object can be used to specify a hit area for displayObjects @@ -36,10 +37,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.ELIP in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.ELIP; } Ellipse.prototype.constructor = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index 5d4a464..ae89148 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -1,4 +1,5 @@ -var Point = require('../Point'); +var Point = require('../Point'), + CONST = require('../../const'); /** * @class @@ -33,6 +34,13 @@ * @member {Point[]} */ this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + */ + this.type = CONST.SHAPES.POLY; } Polygon.prototype.constructor = Polygon; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index c3454b5..16ac62a 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * the Rectangle object is an area defined by its position, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -34,10 +36,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RECT; } Rectangle.prototype.constructor = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0f2abfc..27d286e 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * The Rounded Rectangle object is an area defined by its position and has nice rounded corners, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -41,10 +43,11 @@ this.radius = radius || 20; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RRECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RREC; } RoundedRectangle.prototype.constructor = RoundedRectangle; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js new file mode 100644 index 0000000..42647c5 --- /dev/null +++ b/src/core/primitives/Graphics.js @@ -0,0 +1,1029 @@ +var core = require('../core'), + GraphicsData = require('./GraphicsData'); + +/** + * 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() { + core.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 {object[]} + * @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 = core.CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {object} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object[]} + * @private + */ + 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 core.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(core.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; + } + } + } +}); + +/** + * 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 core.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 core.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; + + 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 core.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 core.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 core.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 core.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 core.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 core.CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = core.Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + core.CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @method _renderWebGL + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderWebGL = function (renderSession) { + // 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 on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + core.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); + + return; + } + else { + renderSession.spriteBatch.stop(); + renderSession.blendModeManager.setBlendMode(this.blendMode); + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + if (this._filters) { + renderSession.filterManager.pushFilter(this._filterBlock); + } + + // check blend mode + if (this.blendMode !== renderSession.spriteBatch.currentBlendMode) { + renderSession.spriteBatch.currentBlendMode = this.blendMode; + + var blendModeWebGL = blendModesWebGL[renderSession.spriteBatch.currentBlendMode]; + + renderSession.spriteBatch.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); + } + + // check if the webgl graphic needs to be updated + if (this.glDirty) { + this.dirty = true; + this.glDirty = false; + } + + core.WebGLGraphics.renderGraphics(this, renderSession); + + // only render if it has children! + if (this.children.length) { + renderSession.spriteBatch.start(); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderWebGL(renderSession); + } + + renderSession.spriteBatch.stop(); + } + + if (this._filters) { + renderSession.filterManager.popFilter(); + } + + if (this._mask) { + renderSession.maskManager.popMask(this.mask, renderSession); + } + + renderSession.drawCount++; + + renderSession.spriteBatch.start(); + } +}; + +/** + * Renders the object using the Canvas renderer + * + * @method _renderCanvas + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderCanvas = function (renderSession) { + // 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; + + core.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession); + + return; + } + else { + var context = renderSession.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderSession.currentBlendMode) { + renderSession.currentBlendMode = this.blendMode; + context.globalCompositeOperation = blendModesCanvas[renderSession.currentBlendMode]; + } + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + var resolution = renderSession.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + core.CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderCanvas(renderSession); + } + + if (this._mask) { + renderSession.maskManager.popMask(renderSession); + } + } +}; + +/** + * 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 core.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 === core.CONST.SHAPES.RECT || type === core.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 === core.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 === core.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 core.CanvasBuffer(bounds.width, bounds.height); + var texture = core.Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new core.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.. + core.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 === core.CONST.SHAPES.POLY) { + data.shape.closed = this.filling; + this.currentPath = data; + } + + this.dirty = true; + + return data; +}; diff --git a/src/core/const.js b/src/core/const.js index 2803e5b..aea72ec 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -130,5 +130,25 @@ backgroundColor: 0x000000, clearBeforeRender: true, preserveDrawingBuffer: false + }, + + /** + * Constants that identify shapes, mainly to prevent `instanceof` calls. + * + * @static + * @constant + * @property {object} SHAPES + * @property {object} SHAPES.POLY=0 + * @property {object} SHAPES.RECT=1 + * @property {object} SHAPES.CIRC=2 + * @property {object} SHAPES.ELIP=3 + * @property {object} SHAPES.RREC=4 + */ + SHAPES: { + POLY = 0; + RECT = 1; + CIRC = 2; + ELIP = 3; + RREC = 4; } }; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index bfd59cf..7c0e7c6 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -95,7 +95,7 @@ this.filterArea = null; /** - * cached sin rotation and cos rotation + * cached sin rotation * * @member {number} * @private @@ -103,7 +103,7 @@ this._sr = 0; /** - * cached sin rotation and cos rotation + * cached cos rotation * * @member {number} * @private @@ -400,7 +400,7 @@ DisplayObject.prototype.generateTexture = function (resolution, scaleMode, renderer) { var bounds = this.getLocalBounds(); - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); _tempMatrix.tx = -bounds.x; _tempMatrix.ty = -bounds.y; @@ -474,7 +474,7 @@ var bounds = this.getLocalBounds(); if (!this._cachedSprite) { - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0); //, renderer); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0); this._cachedSprite = new Sprite(renderTexture); this._cachedSprite.worldTransform = this.worldTransform; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 3f6a3b6..69cfdc9 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Circle object can be used to specify a hit area for displayObjects @@ -29,10 +30,11 @@ this.radius = radius || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.CIRC in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.CIRC; } Circle.prototype.constructor = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index fc9df4d..4f2d066 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Ellipse object can be used to specify a hit area for displayObjects @@ -36,10 +37,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.ELIP in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.ELIP; } Ellipse.prototype.constructor = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index 5d4a464..ae89148 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -1,4 +1,5 @@ -var Point = require('../Point'); +var Point = require('../Point'), + CONST = require('../../const'); /** * @class @@ -33,6 +34,13 @@ * @member {Point[]} */ this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + */ + this.type = CONST.SHAPES.POLY; } Polygon.prototype.constructor = Polygon; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index c3454b5..16ac62a 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * the Rectangle object is an area defined by its position, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -34,10 +36,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RECT; } Rectangle.prototype.constructor = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0f2abfc..27d286e 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * The Rounded Rectangle object is an area defined by its position and has nice rounded corners, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -41,10 +43,11 @@ this.radius = radius || 20; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RRECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RREC; } RoundedRectangle.prototype.constructor = RoundedRectangle; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js new file mode 100644 index 0000000..42647c5 --- /dev/null +++ b/src/core/primitives/Graphics.js @@ -0,0 +1,1029 @@ +var core = require('../core'), + GraphicsData = require('./GraphicsData'); + +/** + * 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() { + core.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 {object[]} + * @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 = core.CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {object} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object[]} + * @private + */ + 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 core.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(core.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; + } + } + } +}); + +/** + * 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 core.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 core.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; + + 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 core.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 core.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 core.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 core.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 core.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 core.CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = core.Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + core.CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @method _renderWebGL + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderWebGL = function (renderSession) { + // 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 on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + core.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); + + return; + } + else { + renderSession.spriteBatch.stop(); + renderSession.blendModeManager.setBlendMode(this.blendMode); + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + if (this._filters) { + renderSession.filterManager.pushFilter(this._filterBlock); + } + + // check blend mode + if (this.blendMode !== renderSession.spriteBatch.currentBlendMode) { + renderSession.spriteBatch.currentBlendMode = this.blendMode; + + var blendModeWebGL = blendModesWebGL[renderSession.spriteBatch.currentBlendMode]; + + renderSession.spriteBatch.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); + } + + // check if the webgl graphic needs to be updated + if (this.glDirty) { + this.dirty = true; + this.glDirty = false; + } + + core.WebGLGraphics.renderGraphics(this, renderSession); + + // only render if it has children! + if (this.children.length) { + renderSession.spriteBatch.start(); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderWebGL(renderSession); + } + + renderSession.spriteBatch.stop(); + } + + if (this._filters) { + renderSession.filterManager.popFilter(); + } + + if (this._mask) { + renderSession.maskManager.popMask(this.mask, renderSession); + } + + renderSession.drawCount++; + + renderSession.spriteBatch.start(); + } +}; + +/** + * Renders the object using the Canvas renderer + * + * @method _renderCanvas + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderCanvas = function (renderSession) { + // 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; + + core.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession); + + return; + } + else { + var context = renderSession.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderSession.currentBlendMode) { + renderSession.currentBlendMode = this.blendMode; + context.globalCompositeOperation = blendModesCanvas[renderSession.currentBlendMode]; + } + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + var resolution = renderSession.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + core.CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderCanvas(renderSession); + } + + if (this._mask) { + renderSession.maskManager.popMask(renderSession); + } + } +}; + +/** + * 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 core.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 === core.CONST.SHAPES.RECT || type === core.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 === core.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 === core.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 core.CanvasBuffer(bounds.width, bounds.height); + var texture = core.Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new core.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.. + core.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 === core.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 new file mode 100644 index 0000000..9377ef7 --- /dev/null +++ b/src/core/primitives/GraphicsData.js @@ -0,0 +1,23 @@ +/** + * 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; diff --git a/src/core/const.js b/src/core/const.js index 2803e5b..aea72ec 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -130,5 +130,25 @@ backgroundColor: 0x000000, clearBeforeRender: true, preserveDrawingBuffer: false + }, + + /** + * Constants that identify shapes, mainly to prevent `instanceof` calls. + * + * @static + * @constant + * @property {object} SHAPES + * @property {object} SHAPES.POLY=0 + * @property {object} SHAPES.RECT=1 + * @property {object} SHAPES.CIRC=2 + * @property {object} SHAPES.ELIP=3 + * @property {object} SHAPES.RREC=4 + */ + SHAPES: { + POLY = 0; + RECT = 1; + CIRC = 2; + ELIP = 3; + RREC = 4; } }; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index bfd59cf..7c0e7c6 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -95,7 +95,7 @@ this.filterArea = null; /** - * cached sin rotation and cos rotation + * cached sin rotation * * @member {number} * @private @@ -103,7 +103,7 @@ this._sr = 0; /** - * cached sin rotation and cos rotation + * cached cos rotation * * @member {number} * @private @@ -400,7 +400,7 @@ DisplayObject.prototype.generateTexture = function (resolution, scaleMode, renderer) { var bounds = this.getLocalBounds(); - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); _tempMatrix.tx = -bounds.x; _tempMatrix.ty = -bounds.y; @@ -474,7 +474,7 @@ var bounds = this.getLocalBounds(); if (!this._cachedSprite) { - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0); //, renderer); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0); this._cachedSprite = new Sprite(renderTexture); this._cachedSprite.worldTransform = this.worldTransform; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 3f6a3b6..69cfdc9 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Circle object can be used to specify a hit area for displayObjects @@ -29,10 +30,11 @@ this.radius = radius || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.CIRC in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.CIRC; } Circle.prototype.constructor = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index fc9df4d..4f2d066 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Ellipse object can be used to specify a hit area for displayObjects @@ -36,10 +37,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.ELIP in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.ELIP; } Ellipse.prototype.constructor = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index 5d4a464..ae89148 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -1,4 +1,5 @@ -var Point = require('../Point'); +var Point = require('../Point'), + CONST = require('../../const'); /** * @class @@ -33,6 +34,13 @@ * @member {Point[]} */ this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + */ + this.type = CONST.SHAPES.POLY; } Polygon.prototype.constructor = Polygon; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index c3454b5..16ac62a 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * the Rectangle object is an area defined by its position, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -34,10 +36,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RECT; } Rectangle.prototype.constructor = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0f2abfc..27d286e 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * The Rounded Rectangle object is an area defined by its position and has nice rounded corners, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -41,10 +43,11 @@ this.radius = radius || 20; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RRECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RREC; } RoundedRectangle.prototype.constructor = RoundedRectangle; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js new file mode 100644 index 0000000..42647c5 --- /dev/null +++ b/src/core/primitives/Graphics.js @@ -0,0 +1,1029 @@ +var core = require('../core'), + GraphicsData = require('./GraphicsData'); + +/** + * 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() { + core.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 {object[]} + * @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 = core.CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {object} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object[]} + * @private + */ + 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 core.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(core.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; + } + } + } +}); + +/** + * 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 core.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 core.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; + + 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 core.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 core.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 core.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 core.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 core.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 core.CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = core.Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + core.CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @method _renderWebGL + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderWebGL = function (renderSession) { + // 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 on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + core.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); + + return; + } + else { + renderSession.spriteBatch.stop(); + renderSession.blendModeManager.setBlendMode(this.blendMode); + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + if (this._filters) { + renderSession.filterManager.pushFilter(this._filterBlock); + } + + // check blend mode + if (this.blendMode !== renderSession.spriteBatch.currentBlendMode) { + renderSession.spriteBatch.currentBlendMode = this.blendMode; + + var blendModeWebGL = blendModesWebGL[renderSession.spriteBatch.currentBlendMode]; + + renderSession.spriteBatch.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); + } + + // check if the webgl graphic needs to be updated + if (this.glDirty) { + this.dirty = true; + this.glDirty = false; + } + + core.WebGLGraphics.renderGraphics(this, renderSession); + + // only render if it has children! + if (this.children.length) { + renderSession.spriteBatch.start(); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderWebGL(renderSession); + } + + renderSession.spriteBatch.stop(); + } + + if (this._filters) { + renderSession.filterManager.popFilter(); + } + + if (this._mask) { + renderSession.maskManager.popMask(this.mask, renderSession); + } + + renderSession.drawCount++; + + renderSession.spriteBatch.start(); + } +}; + +/** + * Renders the object using the Canvas renderer + * + * @method _renderCanvas + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderCanvas = function (renderSession) { + // 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; + + core.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession); + + return; + } + else { + var context = renderSession.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderSession.currentBlendMode) { + renderSession.currentBlendMode = this.blendMode; + context.globalCompositeOperation = blendModesCanvas[renderSession.currentBlendMode]; + } + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + var resolution = renderSession.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + core.CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderCanvas(renderSession); + } + + if (this._mask) { + renderSession.maskManager.popMask(renderSession); + } + } +}; + +/** + * 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 core.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 === core.CONST.SHAPES.RECT || type === core.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 === core.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 === core.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 core.CanvasBuffer(bounds.width, bounds.height); + var texture = core.Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new core.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.. + core.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 === core.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 new file mode 100644 index 0000000..9377ef7 --- /dev/null +++ b/src/core/primitives/GraphicsData.js @@ -0,0 +1,23 @@ +/** + * 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; diff --git a/src/core/primitives/index.js b/src/core/primitives/index.js new file mode 100644 index 0000000..7f02649 --- /dev/null +++ b/src/core/primitives/index.js @@ -0,0 +1,14 @@ +/** + * @file Main export of the PIXI primitives library + * @author Mat Groves + * @copyright 2013-2015 GoodBoyDigital + * @license {@link https://github.com/GoodBoyDigital/pixi.js/blob/master/LICENSE|MIT License} + */ + +/** + * @namespace PIXI + */ +module.exports = { + Graphics: require('./Graphics'), + GraphicsData: require('./GraphicsData') +}; diff --git a/src/core/const.js b/src/core/const.js index 2803e5b..aea72ec 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -130,5 +130,25 @@ backgroundColor: 0x000000, clearBeforeRender: true, preserveDrawingBuffer: false + }, + + /** + * Constants that identify shapes, mainly to prevent `instanceof` calls. + * + * @static + * @constant + * @property {object} SHAPES + * @property {object} SHAPES.POLY=0 + * @property {object} SHAPES.RECT=1 + * @property {object} SHAPES.CIRC=2 + * @property {object} SHAPES.ELIP=3 + * @property {object} SHAPES.RREC=4 + */ + SHAPES: { + POLY = 0; + RECT = 1; + CIRC = 2; + ELIP = 3; + RREC = 4; } }; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index bfd59cf..7c0e7c6 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -95,7 +95,7 @@ this.filterArea = null; /** - * cached sin rotation and cos rotation + * cached sin rotation * * @member {number} * @private @@ -103,7 +103,7 @@ this._sr = 0; /** - * cached sin rotation and cos rotation + * cached cos rotation * * @member {number} * @private @@ -400,7 +400,7 @@ DisplayObject.prototype.generateTexture = function (resolution, scaleMode, renderer) { var bounds = this.getLocalBounds(); - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); _tempMatrix.tx = -bounds.x; _tempMatrix.ty = -bounds.y; @@ -474,7 +474,7 @@ var bounds = this.getLocalBounds(); if (!this._cachedSprite) { - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0); //, renderer); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0); this._cachedSprite = new Sprite(renderTexture); this._cachedSprite.worldTransform = this.worldTransform; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 3f6a3b6..69cfdc9 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Circle object can be used to specify a hit area for displayObjects @@ -29,10 +30,11 @@ this.radius = radius || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.CIRC in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.CIRC; } Circle.prototype.constructor = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index fc9df4d..4f2d066 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Ellipse object can be used to specify a hit area for displayObjects @@ -36,10 +37,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.ELIP in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.ELIP; } Ellipse.prototype.constructor = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index 5d4a464..ae89148 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -1,4 +1,5 @@ -var Point = require('../Point'); +var Point = require('../Point'), + CONST = require('../../const'); /** * @class @@ -33,6 +34,13 @@ * @member {Point[]} */ this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + */ + this.type = CONST.SHAPES.POLY; } Polygon.prototype.constructor = Polygon; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index c3454b5..16ac62a 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * the Rectangle object is an area defined by its position, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -34,10 +36,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RECT; } Rectangle.prototype.constructor = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0f2abfc..27d286e 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * The Rounded Rectangle object is an area defined by its position and has nice rounded corners, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -41,10 +43,11 @@ this.radius = radius || 20; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RRECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RREC; } RoundedRectangle.prototype.constructor = RoundedRectangle; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js new file mode 100644 index 0000000..42647c5 --- /dev/null +++ b/src/core/primitives/Graphics.js @@ -0,0 +1,1029 @@ +var core = require('../core'), + GraphicsData = require('./GraphicsData'); + +/** + * 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() { + core.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 {object[]} + * @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 = core.CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {object} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object[]} + * @private + */ + 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 core.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(core.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; + } + } + } +}); + +/** + * 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 core.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 core.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; + + 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 core.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 core.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 core.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 core.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 core.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 core.CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = core.Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + core.CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @method _renderWebGL + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderWebGL = function (renderSession) { + // 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 on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + core.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); + + return; + } + else { + renderSession.spriteBatch.stop(); + renderSession.blendModeManager.setBlendMode(this.blendMode); + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + if (this._filters) { + renderSession.filterManager.pushFilter(this._filterBlock); + } + + // check blend mode + if (this.blendMode !== renderSession.spriteBatch.currentBlendMode) { + renderSession.spriteBatch.currentBlendMode = this.blendMode; + + var blendModeWebGL = blendModesWebGL[renderSession.spriteBatch.currentBlendMode]; + + renderSession.spriteBatch.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); + } + + // check if the webgl graphic needs to be updated + if (this.glDirty) { + this.dirty = true; + this.glDirty = false; + } + + core.WebGLGraphics.renderGraphics(this, renderSession); + + // only render if it has children! + if (this.children.length) { + renderSession.spriteBatch.start(); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderWebGL(renderSession); + } + + renderSession.spriteBatch.stop(); + } + + if (this._filters) { + renderSession.filterManager.popFilter(); + } + + if (this._mask) { + renderSession.maskManager.popMask(this.mask, renderSession); + } + + renderSession.drawCount++; + + renderSession.spriteBatch.start(); + } +}; + +/** + * Renders the object using the Canvas renderer + * + * @method _renderCanvas + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderCanvas = function (renderSession) { + // 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; + + core.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession); + + return; + } + else { + var context = renderSession.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderSession.currentBlendMode) { + renderSession.currentBlendMode = this.blendMode; + context.globalCompositeOperation = blendModesCanvas[renderSession.currentBlendMode]; + } + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + var resolution = renderSession.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + core.CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderCanvas(renderSession); + } + + if (this._mask) { + renderSession.maskManager.popMask(renderSession); + } + } +}; + +/** + * 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 core.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 === core.CONST.SHAPES.RECT || type === core.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 === core.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 === core.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 core.CanvasBuffer(bounds.width, bounds.height); + var texture = core.Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new core.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.. + core.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 === core.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 new file mode 100644 index 0000000..9377ef7 --- /dev/null +++ b/src/core/primitives/GraphicsData.js @@ -0,0 +1,23 @@ +/** + * 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; diff --git a/src/core/primitives/index.js b/src/core/primitives/index.js new file mode 100644 index 0000000..7f02649 --- /dev/null +++ b/src/core/primitives/index.js @@ -0,0 +1,14 @@ +/** + * @file Main export of the PIXI primitives library + * @author Mat Groves + * @copyright 2013-2015 GoodBoyDigital + * @license {@link https://github.com/GoodBoyDigital/pixi.js/blob/master/LICENSE|MIT License} + */ + +/** + * @namespace PIXI + */ +module.exports = { + Graphics: require('./Graphics'), + GraphicsData: require('./GraphicsData') +}; diff --git a/src/core/renderers/canvas/CanvasGraphics.js b/src/core/renderers/canvas/CanvasGraphics.js index 2b1797a..ef7e765 100644 --- a/src/core/renderers/canvas/CanvasGraphics.js +++ b/src/core/renderers/canvas/CanvasGraphics.js @@ -1,4 +1,4 @@ -var Graphics = require('../../../primitives/Graphics'); +var CONST = require('../../const'); /** * A set of functions used by the canvas renderer to draw the primitive graphics data. @@ -30,7 +30,7 @@ context.lineWidth = data.lineWidth; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -61,7 +61,7 @@ context.stroke(); } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { if (data.fillColor || data.fillColor === 0) { context.globalAlpha = data.fillAlpha * worldAlpha; @@ -75,7 +75,7 @@ context.strokeRect(shape.x, shape.y, shape.width, shape.height); } } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); @@ -92,7 +92,7 @@ context.stroke(); } } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas var w = shape.width * 2; @@ -130,7 +130,7 @@ context.stroke(); } } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var rx = shape.x; var ry = shape.y; var width = shape.width; @@ -190,7 +190,7 @@ var data = graphics.graphicsData[i]; var shape = data.shape; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -207,18 +207,18 @@ } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { context.beginPath(); context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); context.closePath(); } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas @@ -245,7 +245,7 @@ context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); context.closePath(); } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var pts = shape.points; var rx = pts[0]; diff --git a/src/core/const.js b/src/core/const.js index 2803e5b..aea72ec 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -130,5 +130,25 @@ backgroundColor: 0x000000, clearBeforeRender: true, preserveDrawingBuffer: false + }, + + /** + * Constants that identify shapes, mainly to prevent `instanceof` calls. + * + * @static + * @constant + * @property {object} SHAPES + * @property {object} SHAPES.POLY=0 + * @property {object} SHAPES.RECT=1 + * @property {object} SHAPES.CIRC=2 + * @property {object} SHAPES.ELIP=3 + * @property {object} SHAPES.RREC=4 + */ + SHAPES: { + POLY = 0; + RECT = 1; + CIRC = 2; + ELIP = 3; + RREC = 4; } }; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index bfd59cf..7c0e7c6 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -95,7 +95,7 @@ this.filterArea = null; /** - * cached sin rotation and cos rotation + * cached sin rotation * * @member {number} * @private @@ -103,7 +103,7 @@ this._sr = 0; /** - * cached sin rotation and cos rotation + * cached cos rotation * * @member {number} * @private @@ -400,7 +400,7 @@ DisplayObject.prototype.generateTexture = function (resolution, scaleMode, renderer) { var bounds = this.getLocalBounds(); - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); _tempMatrix.tx = -bounds.x; _tempMatrix.ty = -bounds.y; @@ -474,7 +474,7 @@ var bounds = this.getLocalBounds(); if (!this._cachedSprite) { - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0); //, renderer); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0); this._cachedSprite = new Sprite(renderTexture); this._cachedSprite.worldTransform = this.worldTransform; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 3f6a3b6..69cfdc9 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Circle object can be used to specify a hit area for displayObjects @@ -29,10 +30,11 @@ this.radius = radius || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.CIRC in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.CIRC; } Circle.prototype.constructor = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index fc9df4d..4f2d066 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Ellipse object can be used to specify a hit area for displayObjects @@ -36,10 +37,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.ELIP in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.ELIP; } Ellipse.prototype.constructor = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index 5d4a464..ae89148 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -1,4 +1,5 @@ -var Point = require('../Point'); +var Point = require('../Point'), + CONST = require('../../const'); /** * @class @@ -33,6 +34,13 @@ * @member {Point[]} */ this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + */ + this.type = CONST.SHAPES.POLY; } Polygon.prototype.constructor = Polygon; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index c3454b5..16ac62a 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * the Rectangle object is an area defined by its position, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -34,10 +36,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RECT; } Rectangle.prototype.constructor = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0f2abfc..27d286e 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * The Rounded Rectangle object is an area defined by its position and has nice rounded corners, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -41,10 +43,11 @@ this.radius = radius || 20; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RRECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RREC; } RoundedRectangle.prototype.constructor = RoundedRectangle; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js new file mode 100644 index 0000000..42647c5 --- /dev/null +++ b/src/core/primitives/Graphics.js @@ -0,0 +1,1029 @@ +var core = require('../core'), + GraphicsData = require('./GraphicsData'); + +/** + * 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() { + core.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 {object[]} + * @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 = core.CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {object} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object[]} + * @private + */ + 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 core.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(core.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; + } + } + } +}); + +/** + * 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 core.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 core.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; + + 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 core.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 core.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 core.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 core.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 core.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 core.CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = core.Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + core.CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @method _renderWebGL + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderWebGL = function (renderSession) { + // 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 on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + core.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); + + return; + } + else { + renderSession.spriteBatch.stop(); + renderSession.blendModeManager.setBlendMode(this.blendMode); + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + if (this._filters) { + renderSession.filterManager.pushFilter(this._filterBlock); + } + + // check blend mode + if (this.blendMode !== renderSession.spriteBatch.currentBlendMode) { + renderSession.spriteBatch.currentBlendMode = this.blendMode; + + var blendModeWebGL = blendModesWebGL[renderSession.spriteBatch.currentBlendMode]; + + renderSession.spriteBatch.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); + } + + // check if the webgl graphic needs to be updated + if (this.glDirty) { + this.dirty = true; + this.glDirty = false; + } + + core.WebGLGraphics.renderGraphics(this, renderSession); + + // only render if it has children! + if (this.children.length) { + renderSession.spriteBatch.start(); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderWebGL(renderSession); + } + + renderSession.spriteBatch.stop(); + } + + if (this._filters) { + renderSession.filterManager.popFilter(); + } + + if (this._mask) { + renderSession.maskManager.popMask(this.mask, renderSession); + } + + renderSession.drawCount++; + + renderSession.spriteBatch.start(); + } +}; + +/** + * Renders the object using the Canvas renderer + * + * @method _renderCanvas + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderCanvas = function (renderSession) { + // 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; + + core.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession); + + return; + } + else { + var context = renderSession.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderSession.currentBlendMode) { + renderSession.currentBlendMode = this.blendMode; + context.globalCompositeOperation = blendModesCanvas[renderSession.currentBlendMode]; + } + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + var resolution = renderSession.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + core.CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderCanvas(renderSession); + } + + if (this._mask) { + renderSession.maskManager.popMask(renderSession); + } + } +}; + +/** + * 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 core.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 === core.CONST.SHAPES.RECT || type === core.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 === core.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 === core.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 core.CanvasBuffer(bounds.width, bounds.height); + var texture = core.Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new core.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.. + core.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 === core.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 new file mode 100644 index 0000000..9377ef7 --- /dev/null +++ b/src/core/primitives/GraphicsData.js @@ -0,0 +1,23 @@ +/** + * 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; diff --git a/src/core/primitives/index.js b/src/core/primitives/index.js new file mode 100644 index 0000000..7f02649 --- /dev/null +++ b/src/core/primitives/index.js @@ -0,0 +1,14 @@ +/** + * @file Main export of the PIXI primitives library + * @author Mat Groves + * @copyright 2013-2015 GoodBoyDigital + * @license {@link https://github.com/GoodBoyDigital/pixi.js/blob/master/LICENSE|MIT License} + */ + +/** + * @namespace PIXI + */ +module.exports = { + Graphics: require('./Graphics'), + GraphicsData: require('./GraphicsData') +}; diff --git a/src/core/renderers/canvas/CanvasGraphics.js b/src/core/renderers/canvas/CanvasGraphics.js index 2b1797a..ef7e765 100644 --- a/src/core/renderers/canvas/CanvasGraphics.js +++ b/src/core/renderers/canvas/CanvasGraphics.js @@ -1,4 +1,4 @@ -var Graphics = require('../../../primitives/Graphics'); +var CONST = require('../../const'); /** * A set of functions used by the canvas renderer to draw the primitive graphics data. @@ -30,7 +30,7 @@ context.lineWidth = data.lineWidth; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -61,7 +61,7 @@ context.stroke(); } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { if (data.fillColor || data.fillColor === 0) { context.globalAlpha = data.fillAlpha * worldAlpha; @@ -75,7 +75,7 @@ context.strokeRect(shape.x, shape.y, shape.width, shape.height); } } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); @@ -92,7 +92,7 @@ context.stroke(); } } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas var w = shape.width * 2; @@ -130,7 +130,7 @@ context.stroke(); } } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var rx = shape.x; var ry = shape.y; var width = shape.width; @@ -190,7 +190,7 @@ var data = graphics.graphicsData[i]; var shape = data.shape; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -207,18 +207,18 @@ } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { context.beginPath(); context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); context.closePath(); } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas @@ -245,7 +245,7 @@ context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); context.closePath(); } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var pts = shape.points; var rx = pts[0]; diff --git a/src/core/renderers/webgl/managers/WebGLBlendModeManager.js b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js new file mode 100644 index 0000000..9f2f89a --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js @@ -0,0 +1,37 @@ +var WebGLManager = require('./WebGLManager'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLBlendModeManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; +} + +WebGLBlendModeManager.prototype = Object.create(WebGLManager.prototype); +WebGLBlendModeManager.prototype.constructor = WebGLBlendModeManager; +module.exports = WebGLBlendModeManager; + +/** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as BlendModes.ADD + */ +WebGLBlendModeManager.prototype.setBlendMode = function (blendMode) { + if (this.currentBlendMode === blendMode) { + return false; + } + + this.currentBlendMode = blendMode; + + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; +}; diff --git a/src/core/const.js b/src/core/const.js index 2803e5b..aea72ec 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -130,5 +130,25 @@ backgroundColor: 0x000000, clearBeforeRender: true, preserveDrawingBuffer: false + }, + + /** + * Constants that identify shapes, mainly to prevent `instanceof` calls. + * + * @static + * @constant + * @property {object} SHAPES + * @property {object} SHAPES.POLY=0 + * @property {object} SHAPES.RECT=1 + * @property {object} SHAPES.CIRC=2 + * @property {object} SHAPES.ELIP=3 + * @property {object} SHAPES.RREC=4 + */ + SHAPES: { + POLY = 0; + RECT = 1; + CIRC = 2; + ELIP = 3; + RREC = 4; } }; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index bfd59cf..7c0e7c6 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -95,7 +95,7 @@ this.filterArea = null; /** - * cached sin rotation and cos rotation + * cached sin rotation * * @member {number} * @private @@ -103,7 +103,7 @@ this._sr = 0; /** - * cached sin rotation and cos rotation + * cached cos rotation * * @member {number} * @private @@ -400,7 +400,7 @@ DisplayObject.prototype.generateTexture = function (resolution, scaleMode, renderer) { var bounds = this.getLocalBounds(); - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); _tempMatrix.tx = -bounds.x; _tempMatrix.ty = -bounds.y; @@ -474,7 +474,7 @@ var bounds = this.getLocalBounds(); if (!this._cachedSprite) { - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0); //, renderer); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0); this._cachedSprite = new Sprite(renderTexture); this._cachedSprite.worldTransform = this.worldTransform; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 3f6a3b6..69cfdc9 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Circle object can be used to specify a hit area for displayObjects @@ -29,10 +30,11 @@ this.radius = radius || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.CIRC in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.CIRC; } Circle.prototype.constructor = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index fc9df4d..4f2d066 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Ellipse object can be used to specify a hit area for displayObjects @@ -36,10 +37,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.ELIP in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.ELIP; } Ellipse.prototype.constructor = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index 5d4a464..ae89148 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -1,4 +1,5 @@ -var Point = require('../Point'); +var Point = require('../Point'), + CONST = require('../../const'); /** * @class @@ -33,6 +34,13 @@ * @member {Point[]} */ this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + */ + this.type = CONST.SHAPES.POLY; } Polygon.prototype.constructor = Polygon; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index c3454b5..16ac62a 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * the Rectangle object is an area defined by its position, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -34,10 +36,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RECT; } Rectangle.prototype.constructor = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0f2abfc..27d286e 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * The Rounded Rectangle object is an area defined by its position and has nice rounded corners, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -41,10 +43,11 @@ this.radius = radius || 20; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RRECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RREC; } RoundedRectangle.prototype.constructor = RoundedRectangle; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js new file mode 100644 index 0000000..42647c5 --- /dev/null +++ b/src/core/primitives/Graphics.js @@ -0,0 +1,1029 @@ +var core = require('../core'), + GraphicsData = require('./GraphicsData'); + +/** + * 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() { + core.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 {object[]} + * @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 = core.CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {object} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object[]} + * @private + */ + 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 core.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(core.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; + } + } + } +}); + +/** + * 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 core.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 core.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; + + 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 core.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 core.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 core.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 core.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 core.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 core.CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = core.Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + core.CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @method _renderWebGL + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderWebGL = function (renderSession) { + // 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 on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + core.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); + + return; + } + else { + renderSession.spriteBatch.stop(); + renderSession.blendModeManager.setBlendMode(this.blendMode); + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + if (this._filters) { + renderSession.filterManager.pushFilter(this._filterBlock); + } + + // check blend mode + if (this.blendMode !== renderSession.spriteBatch.currentBlendMode) { + renderSession.spriteBatch.currentBlendMode = this.blendMode; + + var blendModeWebGL = blendModesWebGL[renderSession.spriteBatch.currentBlendMode]; + + renderSession.spriteBatch.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); + } + + // check if the webgl graphic needs to be updated + if (this.glDirty) { + this.dirty = true; + this.glDirty = false; + } + + core.WebGLGraphics.renderGraphics(this, renderSession); + + // only render if it has children! + if (this.children.length) { + renderSession.spriteBatch.start(); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderWebGL(renderSession); + } + + renderSession.spriteBatch.stop(); + } + + if (this._filters) { + renderSession.filterManager.popFilter(); + } + + if (this._mask) { + renderSession.maskManager.popMask(this.mask, renderSession); + } + + renderSession.drawCount++; + + renderSession.spriteBatch.start(); + } +}; + +/** + * Renders the object using the Canvas renderer + * + * @method _renderCanvas + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderCanvas = function (renderSession) { + // 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; + + core.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession); + + return; + } + else { + var context = renderSession.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderSession.currentBlendMode) { + renderSession.currentBlendMode = this.blendMode; + context.globalCompositeOperation = blendModesCanvas[renderSession.currentBlendMode]; + } + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + var resolution = renderSession.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + core.CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderCanvas(renderSession); + } + + if (this._mask) { + renderSession.maskManager.popMask(renderSession); + } + } +}; + +/** + * 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 core.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 === core.CONST.SHAPES.RECT || type === core.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 === core.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 === core.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 core.CanvasBuffer(bounds.width, bounds.height); + var texture = core.Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new core.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.. + core.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 === core.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 new file mode 100644 index 0000000..9377ef7 --- /dev/null +++ b/src/core/primitives/GraphicsData.js @@ -0,0 +1,23 @@ +/** + * 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; diff --git a/src/core/primitives/index.js b/src/core/primitives/index.js new file mode 100644 index 0000000..7f02649 --- /dev/null +++ b/src/core/primitives/index.js @@ -0,0 +1,14 @@ +/** + * @file Main export of the PIXI primitives library + * @author Mat Groves + * @copyright 2013-2015 GoodBoyDigital + * @license {@link https://github.com/GoodBoyDigital/pixi.js/blob/master/LICENSE|MIT License} + */ + +/** + * @namespace PIXI + */ +module.exports = { + Graphics: require('./Graphics'), + GraphicsData: require('./GraphicsData') +}; diff --git a/src/core/renderers/canvas/CanvasGraphics.js b/src/core/renderers/canvas/CanvasGraphics.js index 2b1797a..ef7e765 100644 --- a/src/core/renderers/canvas/CanvasGraphics.js +++ b/src/core/renderers/canvas/CanvasGraphics.js @@ -1,4 +1,4 @@ -var Graphics = require('../../../primitives/Graphics'); +var CONST = require('../../const'); /** * A set of functions used by the canvas renderer to draw the primitive graphics data. @@ -30,7 +30,7 @@ context.lineWidth = data.lineWidth; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -61,7 +61,7 @@ context.stroke(); } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { if (data.fillColor || data.fillColor === 0) { context.globalAlpha = data.fillAlpha * worldAlpha; @@ -75,7 +75,7 @@ context.strokeRect(shape.x, shape.y, shape.width, shape.height); } } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); @@ -92,7 +92,7 @@ context.stroke(); } } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas var w = shape.width * 2; @@ -130,7 +130,7 @@ context.stroke(); } } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var rx = shape.x; var ry = shape.y; var width = shape.width; @@ -190,7 +190,7 @@ var data = graphics.graphicsData[i]; var shape = data.shape; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -207,18 +207,18 @@ } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { context.beginPath(); context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); context.closePath(); } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas @@ -245,7 +245,7 @@ context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); context.closePath(); } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var pts = shape.points; var rx = pts[0]; diff --git a/src/core/renderers/webgl/managers/WebGLBlendModeManager.js b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js new file mode 100644 index 0000000..9f2f89a --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js @@ -0,0 +1,37 @@ +var WebGLManager = require('./WebGLManager'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLBlendModeManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; +} + +WebGLBlendModeManager.prototype = Object.create(WebGLManager.prototype); +WebGLBlendModeManager.prototype.constructor = WebGLBlendModeManager; +module.exports = WebGLBlendModeManager; + +/** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as BlendModes.ADD + */ +WebGLBlendModeManager.prototype.setBlendMode = function (blendMode) { + if (this.currentBlendMode === blendMode) { + return false; + } + + this.currentBlendMode = blendMode; + + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; +}; diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js new file mode 100644 index 0000000..176e7cd --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -0,0 +1,446 @@ +var WebGLManager = require('./WebGLManager'), + FilterTexture = require('./FilterTexture'), + PixiShader = require('../shaders/PixiShader'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLFilterManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {any[]} + */ + this.filterStack = []; + + /** + * @member {any[]]} + */ + this.texturePool = []; + + /** + * @member {number} + */ + this.offsetX = 0; + + /** + * @member {number} + */ + this.offsetY = 0; + + // listen for context and update necessary buffers + var self = this; + this.renderer.on('context', function () { + self.texturePool.length = 0; + self.initShaderBuffers(); + }); +} + +WebGLFilterManager.prototype = Object.create(WebGLManager.prototype); +WebGLFilterManager.prototype.constructor = WebGLFilterManager; +module.exports = WebGLFilterManager; + +/** + * @param renderer {WebGLRenderer} + * @param buffer {ArrayBuffer} + */ +WebGLFilterManager.prototype.begin = function (renderer, buffer) { + this.renderer = renderer; + this.defaultShader = renderer.shaderManager.defaultShader; + + this.width = renderer.projection.x * 2; + this.height = -renderer.projection.y * 2; + + this.buffer = buffer; +}; + +/** + * Applies the filter and adds it to the current filter stack. + * + * @param filterBlock {object} the filter that will be pushed to the current filter stack + */ +WebGLFilterManager.prototype.pushFilter = function (filterBlock) { + var gl = this.renderer.gl; + + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds(); + + // filter program + // OPTIMISATION - the first filter is free if its a simple color change? + this.filterStack.push(filterBlock); + + var filter = filterBlock.filterPasses[0]; + + this.offsetX += filterBlock._filterArea.x; + this.offsetY += filterBlock._filterArea.y; + + var texture = this.texturePool.pop(); + if (!texture) { + texture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + else { + texture.resize(this.width, this.height); + } + + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea; + + var padding = filter.padding; + filterArea.x -= padding; + filterArea.y -= padding; + filterArea.width += padding * 2; + filterArea.height += padding * 2; + + // cap filter to screen size.. + if (filterArea.x < 0) { + filterArea.x = 0; + } + + if (filterArea.width > this.width) { + filterArea.width = this.width; + } + + if (filterArea.y < 0) { + filterArea.y = 0; + } + + if (filterArea.height > this.height) { + filterArea.height = this.height; + } + + //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); + + // set view port + gl.viewport(0, 0, filterArea.width, filterArea.height); + + projection.x = filterArea.width/2; + projection.y = -filterArea.height/2; + + offset.x = -filterArea.x; + offset.y = -filterArea.y; + + // update projection + // now restore the regular shader.. + // this.renderer.shaderManager.setShader(this.defaultShader); + //gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); + //gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y); + + gl.colorMask(true, true, true, true); + gl.clearColor(0,0,0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + + filterBlock._glFilterTexture = texture; + +}; + +/** + * Removes the last filter from the filter stack and doesn't return it. + * + */ +WebGLFilterManager.prototype.popFilter = function () { + var gl = this.renderer.gl; + + var filterBlock = this.filterStack.pop(); + var filterArea = filterBlock._filterArea; + var texture = filterBlock._glFilterTexture; + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + if (filterBlock.filterPasses.length > 1) { + gl.viewport(0, 0, filterArea.width, filterArea.height); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = 0; + this.vertexArray[1] = filterArea.height; + + this.vertexArray[2] = filterArea.width; + this.vertexArray[3] = filterArea.height; + + this.vertexArray[4] = 0; + this.vertexArray[5] = 0; + + this.vertexArray[6] = filterArea.width; + this.vertexArray[7] = 0; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + // now set the uvs.. + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + var inputTexture = texture; + var outputTexture = this.texturePool.pop(); + if (!outputTexture) { + outputTexture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + outputTexture.resize(this.width, this.height); + + // need to clear this FBO as it may have some left over elements from a previous filter. + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + gl.clear(gl.COLOR_BUFFER_BIT); + + gl.disable(gl.BLEND); + + for (var i = 0; i < filterBlock.filterPasses.length-1; i++) { + var filterPass = filterBlock.filterPasses[i]; + + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); + + // draw texture.. + //filterPass.applyFilterPass(filterArea.width, filterArea.height); + this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height); + + // swap the textures.. + var temp = inputTexture; + inputTexture = outputTexture; + outputTexture = temp; + } + + gl.enable(gl.BLEND); + + texture = inputTexture; + this.texturePool.push(outputTexture); + } + + var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1]; + + this.offsetX -= filterArea.x; + this.offsetY -= filterArea.y; + + var sizeX = this.width; + var sizeY = this.height; + + var offsetX = 0; + var offsetY = 0; + + var buffer = this.buffer; + + // time to render the filters texture to the previous scene + if (this.filterStack.length === 0) { + gl.colorMask(true, true, true, true);//this.transparent); + } + else { + var currentFilter = this.filterStack[this.filterStack.length-1]; + filterArea = currentFilter._filterArea; + + sizeX = filterArea.width; + sizeY = filterArea.height; + + offsetX = filterArea.x; + offsetY = filterArea.y; + + buffer = currentFilter._glFilterTexture.frameBuffer; + } + + // TODO need to remove these global elements.. + projection.x = sizeX/2; + projection.y = -sizeY/2; + + offset.x = offsetX; + offset.y = offsetY; + + filterArea = filterBlock._filterArea; + + var x = filterArea.x-offsetX; + var y = filterArea.y-offsetY; + + // update the buffers.. + // make sure to flip the y! + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = x; + this.vertexArray[1] = y + filterArea.height; + + this.vertexArray[2] = x + filterArea.width; + this.vertexArray[3] = y + filterArea.height; + + this.vertexArray[4] = x; + this.vertexArray[5] = y; + + this.vertexArray[6] = x + filterArea.width; + this.vertexArray[7] = y; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + gl.viewport(0, 0, sizeX, sizeY); + + // bind the buffer + gl.bindFramebuffer(gl.FRAMEBUFFER, buffer ); + + // set the blend mode! + //gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + // apply! + this.applyFilterPass(filter, filterArea, sizeX, sizeY); + + // now restore the regular shader.. should happen automatically now.. + // this.renderer.shaderManager.setShader(this.defaultShader); + // gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2); + // gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY); + + // return the texture to the pool + this.texturePool.push(texture); + filterBlock._glFilterTexture = null; +}; + + +/** + * Applies the filter to the specified area. + * + * @param filter {AbstractFilter} the filter that needs to be applied + * @param filterArea {Texture} TODO - might need an update + * @param width {number} the horizontal range of the filter + * @param height {number} the vertical range of the filter + */ +WebGLFilterManager.prototype.applyFilterPass = function (filter, filterArea, width, height) { + // use program + var gl = this.renderer.gl; + + var shader = filter.shaders[gl.id]; + + if (!shader) { + shader = new PixiShader(gl); + + shader.fragmentSrc = filter.fragmentSrc; + shader.uniforms = filter.uniforms; + shader.init(); + + filter.shaders[gl.id] = shader; + } + + // set the shader + this.renderer.shaderManager.setShader(shader); + +// gl.useProgram(shader.program); + + gl.uniform2f(shader.projectionVector, width/2, -height/2); + gl.uniform2f(shader.offsetVector, 0,0); + + if (filter.uniforms.dimensions) { + filter.uniforms.dimensions.value[0] = this.width;//width; + filter.uniforms.dimensions.value[1] = this.height;//height; + filter.uniforms.dimensions.value[2] = this.vertexArray[0]; + filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; + } + + shader.syncUniforms(); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + + // draw the filter... + gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); + + this.renderer.drawCount++; +}; + +/** + * Initialises the shader buffers. + * + */ +WebGLFilterManager.prototype.initShaderBuffers = function () { + var gl = this.renderer.gl; + + // create some buffers + this.vertexBuffer = gl.createBuffer(); + this.uvBuffer = gl.createBuffer(); + this.colorBuffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + + // bind and upload the vertexs.. + // keep a reference to the vertexFloatData.. + this.vertexArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW); + + // bind and upload the uv buffer + this.uvArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.uvArray, gl.STATIC_DRAW); + + this.colorArray = new Float32Array([1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.colorArray, gl.STATIC_DRAW); + + // bind and upload the index + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW); + +}; + +/** + * Destroys the filter and removes it from the filter stack. + * + */ +WebGLFilterManager.prototype.destroy = function () { + var gl = this.renderer.gl; + + this.filterStack = null; + + this.offsetX = 0; + this.offsetY = 0; + + // destroy textures + for (var i = 0; i < this.texturePool.length; i++) { + this.texturePool[i].destroy(); + } + + this.texturePool = null; + + //destroy buffers.. + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.uvBuffer); + gl.deleteBuffer(this.colorBuffer); + gl.deleteBuffer(this.indexBuffer); + + this.renderer = null; +}; diff --git a/src/core/const.js b/src/core/const.js index 2803e5b..aea72ec 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -130,5 +130,25 @@ backgroundColor: 0x000000, clearBeforeRender: true, preserveDrawingBuffer: false + }, + + /** + * Constants that identify shapes, mainly to prevent `instanceof` calls. + * + * @static + * @constant + * @property {object} SHAPES + * @property {object} SHAPES.POLY=0 + * @property {object} SHAPES.RECT=1 + * @property {object} SHAPES.CIRC=2 + * @property {object} SHAPES.ELIP=3 + * @property {object} SHAPES.RREC=4 + */ + SHAPES: { + POLY = 0; + RECT = 1; + CIRC = 2; + ELIP = 3; + RREC = 4; } }; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index bfd59cf..7c0e7c6 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -95,7 +95,7 @@ this.filterArea = null; /** - * cached sin rotation and cos rotation + * cached sin rotation * * @member {number} * @private @@ -103,7 +103,7 @@ this._sr = 0; /** - * cached sin rotation and cos rotation + * cached cos rotation * * @member {number} * @private @@ -400,7 +400,7 @@ DisplayObject.prototype.generateTexture = function (resolution, scaleMode, renderer) { var bounds = this.getLocalBounds(); - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); _tempMatrix.tx = -bounds.x; _tempMatrix.ty = -bounds.y; @@ -474,7 +474,7 @@ var bounds = this.getLocalBounds(); if (!this._cachedSprite) { - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0); //, renderer); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0); this._cachedSprite = new Sprite(renderTexture); this._cachedSprite.worldTransform = this.worldTransform; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 3f6a3b6..69cfdc9 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Circle object can be used to specify a hit area for displayObjects @@ -29,10 +30,11 @@ this.radius = radius || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.CIRC in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.CIRC; } Circle.prototype.constructor = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index fc9df4d..4f2d066 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Ellipse object can be used to specify a hit area for displayObjects @@ -36,10 +37,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.ELIP in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.ELIP; } Ellipse.prototype.constructor = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index 5d4a464..ae89148 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -1,4 +1,5 @@ -var Point = require('../Point'); +var Point = require('../Point'), + CONST = require('../../const'); /** * @class @@ -33,6 +34,13 @@ * @member {Point[]} */ this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + */ + this.type = CONST.SHAPES.POLY; } Polygon.prototype.constructor = Polygon; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index c3454b5..16ac62a 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * the Rectangle object is an area defined by its position, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -34,10 +36,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RECT; } Rectangle.prototype.constructor = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0f2abfc..27d286e 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * The Rounded Rectangle object is an area defined by its position and has nice rounded corners, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -41,10 +43,11 @@ this.radius = radius || 20; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RRECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RREC; } RoundedRectangle.prototype.constructor = RoundedRectangle; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js new file mode 100644 index 0000000..42647c5 --- /dev/null +++ b/src/core/primitives/Graphics.js @@ -0,0 +1,1029 @@ +var core = require('../core'), + GraphicsData = require('./GraphicsData'); + +/** + * 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() { + core.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 {object[]} + * @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 = core.CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {object} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object[]} + * @private + */ + 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 core.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(core.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; + } + } + } +}); + +/** + * 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 core.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 core.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; + + 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 core.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 core.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 core.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 core.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 core.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 core.CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = core.Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + core.CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @method _renderWebGL + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderWebGL = function (renderSession) { + // 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 on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + core.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); + + return; + } + else { + renderSession.spriteBatch.stop(); + renderSession.blendModeManager.setBlendMode(this.blendMode); + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + if (this._filters) { + renderSession.filterManager.pushFilter(this._filterBlock); + } + + // check blend mode + if (this.blendMode !== renderSession.spriteBatch.currentBlendMode) { + renderSession.spriteBatch.currentBlendMode = this.blendMode; + + var blendModeWebGL = blendModesWebGL[renderSession.spriteBatch.currentBlendMode]; + + renderSession.spriteBatch.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); + } + + // check if the webgl graphic needs to be updated + if (this.glDirty) { + this.dirty = true; + this.glDirty = false; + } + + core.WebGLGraphics.renderGraphics(this, renderSession); + + // only render if it has children! + if (this.children.length) { + renderSession.spriteBatch.start(); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderWebGL(renderSession); + } + + renderSession.spriteBatch.stop(); + } + + if (this._filters) { + renderSession.filterManager.popFilter(); + } + + if (this._mask) { + renderSession.maskManager.popMask(this.mask, renderSession); + } + + renderSession.drawCount++; + + renderSession.spriteBatch.start(); + } +}; + +/** + * Renders the object using the Canvas renderer + * + * @method _renderCanvas + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderCanvas = function (renderSession) { + // 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; + + core.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession); + + return; + } + else { + var context = renderSession.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderSession.currentBlendMode) { + renderSession.currentBlendMode = this.blendMode; + context.globalCompositeOperation = blendModesCanvas[renderSession.currentBlendMode]; + } + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + var resolution = renderSession.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + core.CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderCanvas(renderSession); + } + + if (this._mask) { + renderSession.maskManager.popMask(renderSession); + } + } +}; + +/** + * 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 core.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 === core.CONST.SHAPES.RECT || type === core.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 === core.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 === core.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 core.CanvasBuffer(bounds.width, bounds.height); + var texture = core.Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new core.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.. + core.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 === core.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 new file mode 100644 index 0000000..9377ef7 --- /dev/null +++ b/src/core/primitives/GraphicsData.js @@ -0,0 +1,23 @@ +/** + * 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; diff --git a/src/core/primitives/index.js b/src/core/primitives/index.js new file mode 100644 index 0000000..7f02649 --- /dev/null +++ b/src/core/primitives/index.js @@ -0,0 +1,14 @@ +/** + * @file Main export of the PIXI primitives library + * @author Mat Groves + * @copyright 2013-2015 GoodBoyDigital + * @license {@link https://github.com/GoodBoyDigital/pixi.js/blob/master/LICENSE|MIT License} + */ + +/** + * @namespace PIXI + */ +module.exports = { + Graphics: require('./Graphics'), + GraphicsData: require('./GraphicsData') +}; diff --git a/src/core/renderers/canvas/CanvasGraphics.js b/src/core/renderers/canvas/CanvasGraphics.js index 2b1797a..ef7e765 100644 --- a/src/core/renderers/canvas/CanvasGraphics.js +++ b/src/core/renderers/canvas/CanvasGraphics.js @@ -1,4 +1,4 @@ -var Graphics = require('../../../primitives/Graphics'); +var CONST = require('../../const'); /** * A set of functions used by the canvas renderer to draw the primitive graphics data. @@ -30,7 +30,7 @@ context.lineWidth = data.lineWidth; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -61,7 +61,7 @@ context.stroke(); } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { if (data.fillColor || data.fillColor === 0) { context.globalAlpha = data.fillAlpha * worldAlpha; @@ -75,7 +75,7 @@ context.strokeRect(shape.x, shape.y, shape.width, shape.height); } } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); @@ -92,7 +92,7 @@ context.stroke(); } } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas var w = shape.width * 2; @@ -130,7 +130,7 @@ context.stroke(); } } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var rx = shape.x; var ry = shape.y; var width = shape.width; @@ -190,7 +190,7 @@ var data = graphics.graphicsData[i]; var shape = data.shape; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -207,18 +207,18 @@ } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { context.beginPath(); context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); context.closePath(); } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas @@ -245,7 +245,7 @@ context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); context.closePath(); } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var pts = shape.points; var rx = pts[0]; diff --git a/src/core/renderers/webgl/managers/WebGLBlendModeManager.js b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js new file mode 100644 index 0000000..9f2f89a --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js @@ -0,0 +1,37 @@ +var WebGLManager = require('./WebGLManager'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLBlendModeManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; +} + +WebGLBlendModeManager.prototype = Object.create(WebGLManager.prototype); +WebGLBlendModeManager.prototype.constructor = WebGLBlendModeManager; +module.exports = WebGLBlendModeManager; + +/** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as BlendModes.ADD + */ +WebGLBlendModeManager.prototype.setBlendMode = function (blendMode) { + if (this.currentBlendMode === blendMode) { + return false; + } + + this.currentBlendMode = blendMode; + + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; +}; diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js new file mode 100644 index 0000000..176e7cd --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -0,0 +1,446 @@ +var WebGLManager = require('./WebGLManager'), + FilterTexture = require('./FilterTexture'), + PixiShader = require('../shaders/PixiShader'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLFilterManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {any[]} + */ + this.filterStack = []; + + /** + * @member {any[]]} + */ + this.texturePool = []; + + /** + * @member {number} + */ + this.offsetX = 0; + + /** + * @member {number} + */ + this.offsetY = 0; + + // listen for context and update necessary buffers + var self = this; + this.renderer.on('context', function () { + self.texturePool.length = 0; + self.initShaderBuffers(); + }); +} + +WebGLFilterManager.prototype = Object.create(WebGLManager.prototype); +WebGLFilterManager.prototype.constructor = WebGLFilterManager; +module.exports = WebGLFilterManager; + +/** + * @param renderer {WebGLRenderer} + * @param buffer {ArrayBuffer} + */ +WebGLFilterManager.prototype.begin = function (renderer, buffer) { + this.renderer = renderer; + this.defaultShader = renderer.shaderManager.defaultShader; + + this.width = renderer.projection.x * 2; + this.height = -renderer.projection.y * 2; + + this.buffer = buffer; +}; + +/** + * Applies the filter and adds it to the current filter stack. + * + * @param filterBlock {object} the filter that will be pushed to the current filter stack + */ +WebGLFilterManager.prototype.pushFilter = function (filterBlock) { + var gl = this.renderer.gl; + + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds(); + + // filter program + // OPTIMISATION - the first filter is free if its a simple color change? + this.filterStack.push(filterBlock); + + var filter = filterBlock.filterPasses[0]; + + this.offsetX += filterBlock._filterArea.x; + this.offsetY += filterBlock._filterArea.y; + + var texture = this.texturePool.pop(); + if (!texture) { + texture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + else { + texture.resize(this.width, this.height); + } + + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea; + + var padding = filter.padding; + filterArea.x -= padding; + filterArea.y -= padding; + filterArea.width += padding * 2; + filterArea.height += padding * 2; + + // cap filter to screen size.. + if (filterArea.x < 0) { + filterArea.x = 0; + } + + if (filterArea.width > this.width) { + filterArea.width = this.width; + } + + if (filterArea.y < 0) { + filterArea.y = 0; + } + + if (filterArea.height > this.height) { + filterArea.height = this.height; + } + + //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); + + // set view port + gl.viewport(0, 0, filterArea.width, filterArea.height); + + projection.x = filterArea.width/2; + projection.y = -filterArea.height/2; + + offset.x = -filterArea.x; + offset.y = -filterArea.y; + + // update projection + // now restore the regular shader.. + // this.renderer.shaderManager.setShader(this.defaultShader); + //gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); + //gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y); + + gl.colorMask(true, true, true, true); + gl.clearColor(0,0,0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + + filterBlock._glFilterTexture = texture; + +}; + +/** + * Removes the last filter from the filter stack and doesn't return it. + * + */ +WebGLFilterManager.prototype.popFilter = function () { + var gl = this.renderer.gl; + + var filterBlock = this.filterStack.pop(); + var filterArea = filterBlock._filterArea; + var texture = filterBlock._glFilterTexture; + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + if (filterBlock.filterPasses.length > 1) { + gl.viewport(0, 0, filterArea.width, filterArea.height); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = 0; + this.vertexArray[1] = filterArea.height; + + this.vertexArray[2] = filterArea.width; + this.vertexArray[3] = filterArea.height; + + this.vertexArray[4] = 0; + this.vertexArray[5] = 0; + + this.vertexArray[6] = filterArea.width; + this.vertexArray[7] = 0; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + // now set the uvs.. + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + var inputTexture = texture; + var outputTexture = this.texturePool.pop(); + if (!outputTexture) { + outputTexture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + outputTexture.resize(this.width, this.height); + + // need to clear this FBO as it may have some left over elements from a previous filter. + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + gl.clear(gl.COLOR_BUFFER_BIT); + + gl.disable(gl.BLEND); + + for (var i = 0; i < filterBlock.filterPasses.length-1; i++) { + var filterPass = filterBlock.filterPasses[i]; + + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); + + // draw texture.. + //filterPass.applyFilterPass(filterArea.width, filterArea.height); + this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height); + + // swap the textures.. + var temp = inputTexture; + inputTexture = outputTexture; + outputTexture = temp; + } + + gl.enable(gl.BLEND); + + texture = inputTexture; + this.texturePool.push(outputTexture); + } + + var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1]; + + this.offsetX -= filterArea.x; + this.offsetY -= filterArea.y; + + var sizeX = this.width; + var sizeY = this.height; + + var offsetX = 0; + var offsetY = 0; + + var buffer = this.buffer; + + // time to render the filters texture to the previous scene + if (this.filterStack.length === 0) { + gl.colorMask(true, true, true, true);//this.transparent); + } + else { + var currentFilter = this.filterStack[this.filterStack.length-1]; + filterArea = currentFilter._filterArea; + + sizeX = filterArea.width; + sizeY = filterArea.height; + + offsetX = filterArea.x; + offsetY = filterArea.y; + + buffer = currentFilter._glFilterTexture.frameBuffer; + } + + // TODO need to remove these global elements.. + projection.x = sizeX/2; + projection.y = -sizeY/2; + + offset.x = offsetX; + offset.y = offsetY; + + filterArea = filterBlock._filterArea; + + var x = filterArea.x-offsetX; + var y = filterArea.y-offsetY; + + // update the buffers.. + // make sure to flip the y! + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = x; + this.vertexArray[1] = y + filterArea.height; + + this.vertexArray[2] = x + filterArea.width; + this.vertexArray[3] = y + filterArea.height; + + this.vertexArray[4] = x; + this.vertexArray[5] = y; + + this.vertexArray[6] = x + filterArea.width; + this.vertexArray[7] = y; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + gl.viewport(0, 0, sizeX, sizeY); + + // bind the buffer + gl.bindFramebuffer(gl.FRAMEBUFFER, buffer ); + + // set the blend mode! + //gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + // apply! + this.applyFilterPass(filter, filterArea, sizeX, sizeY); + + // now restore the regular shader.. should happen automatically now.. + // this.renderer.shaderManager.setShader(this.defaultShader); + // gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2); + // gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY); + + // return the texture to the pool + this.texturePool.push(texture); + filterBlock._glFilterTexture = null; +}; + + +/** + * Applies the filter to the specified area. + * + * @param filter {AbstractFilter} the filter that needs to be applied + * @param filterArea {Texture} TODO - might need an update + * @param width {number} the horizontal range of the filter + * @param height {number} the vertical range of the filter + */ +WebGLFilterManager.prototype.applyFilterPass = function (filter, filterArea, width, height) { + // use program + var gl = this.renderer.gl; + + var shader = filter.shaders[gl.id]; + + if (!shader) { + shader = new PixiShader(gl); + + shader.fragmentSrc = filter.fragmentSrc; + shader.uniforms = filter.uniforms; + shader.init(); + + filter.shaders[gl.id] = shader; + } + + // set the shader + this.renderer.shaderManager.setShader(shader); + +// gl.useProgram(shader.program); + + gl.uniform2f(shader.projectionVector, width/2, -height/2); + gl.uniform2f(shader.offsetVector, 0,0); + + if (filter.uniforms.dimensions) { + filter.uniforms.dimensions.value[0] = this.width;//width; + filter.uniforms.dimensions.value[1] = this.height;//height; + filter.uniforms.dimensions.value[2] = this.vertexArray[0]; + filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; + } + + shader.syncUniforms(); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + + // draw the filter... + gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); + + this.renderer.drawCount++; +}; + +/** + * Initialises the shader buffers. + * + */ +WebGLFilterManager.prototype.initShaderBuffers = function () { + var gl = this.renderer.gl; + + // create some buffers + this.vertexBuffer = gl.createBuffer(); + this.uvBuffer = gl.createBuffer(); + this.colorBuffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + + // bind and upload the vertexs.. + // keep a reference to the vertexFloatData.. + this.vertexArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW); + + // bind and upload the uv buffer + this.uvArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.uvArray, gl.STATIC_DRAW); + + this.colorArray = new Float32Array([1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.colorArray, gl.STATIC_DRAW); + + // bind and upload the index + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW); + +}; + +/** + * Destroys the filter and removes it from the filter stack. + * + */ +WebGLFilterManager.prototype.destroy = function () { + var gl = this.renderer.gl; + + this.filterStack = null; + + this.offsetX = 0; + this.offsetY = 0; + + // destroy textures + for (var i = 0; i < this.texturePool.length; i++) { + this.texturePool[i].destroy(); + } + + this.texturePool = null; + + //destroy buffers.. + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.uvBuffer); + gl.deleteBuffer(this.colorBuffer); + gl.deleteBuffer(this.indexBuffer); + + this.renderer = null; +}; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js new file mode 100644 index 0000000..9d47b5c --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -0,0 +1,20 @@ +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLManager(renderer) { + /** + * The renderer this manager works for. + * + * @member {WebGLRenderer} + */ + this.renderer = renderer; +} + +WebGLManager.prototype.constructor = WebGLManager; +module.exports = WebGLManager; + +WebGLManager.prototype.destroy = function () { + this.renderer = null; +}; diff --git a/src/core/const.js b/src/core/const.js index 2803e5b..aea72ec 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -130,5 +130,25 @@ backgroundColor: 0x000000, clearBeforeRender: true, preserveDrawingBuffer: false + }, + + /** + * Constants that identify shapes, mainly to prevent `instanceof` calls. + * + * @static + * @constant + * @property {object} SHAPES + * @property {object} SHAPES.POLY=0 + * @property {object} SHAPES.RECT=1 + * @property {object} SHAPES.CIRC=2 + * @property {object} SHAPES.ELIP=3 + * @property {object} SHAPES.RREC=4 + */ + SHAPES: { + POLY = 0; + RECT = 1; + CIRC = 2; + ELIP = 3; + RREC = 4; } }; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index bfd59cf..7c0e7c6 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -95,7 +95,7 @@ this.filterArea = null; /** - * cached sin rotation and cos rotation + * cached sin rotation * * @member {number} * @private @@ -103,7 +103,7 @@ this._sr = 0; /** - * cached sin rotation and cos rotation + * cached cos rotation * * @member {number} * @private @@ -400,7 +400,7 @@ DisplayObject.prototype.generateTexture = function (resolution, scaleMode, renderer) { var bounds = this.getLocalBounds(); - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); _tempMatrix.tx = -bounds.x; _tempMatrix.ty = -bounds.y; @@ -474,7 +474,7 @@ var bounds = this.getLocalBounds(); if (!this._cachedSprite) { - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0); //, renderer); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0); this._cachedSprite = new Sprite(renderTexture); this._cachedSprite.worldTransform = this.worldTransform; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 3f6a3b6..69cfdc9 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Circle object can be used to specify a hit area for displayObjects @@ -29,10 +30,11 @@ this.radius = radius || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.CIRC in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.CIRC; } Circle.prototype.constructor = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index fc9df4d..4f2d066 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Ellipse object can be used to specify a hit area for displayObjects @@ -36,10 +37,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.ELIP in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.ELIP; } Ellipse.prototype.constructor = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index 5d4a464..ae89148 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -1,4 +1,5 @@ -var Point = require('../Point'); +var Point = require('../Point'), + CONST = require('../../const'); /** * @class @@ -33,6 +34,13 @@ * @member {Point[]} */ this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + */ + this.type = CONST.SHAPES.POLY; } Polygon.prototype.constructor = Polygon; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index c3454b5..16ac62a 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * the Rectangle object is an area defined by its position, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -34,10 +36,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RECT; } Rectangle.prototype.constructor = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0f2abfc..27d286e 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * The Rounded Rectangle object is an area defined by its position and has nice rounded corners, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -41,10 +43,11 @@ this.radius = radius || 20; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RRECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RREC; } RoundedRectangle.prototype.constructor = RoundedRectangle; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js new file mode 100644 index 0000000..42647c5 --- /dev/null +++ b/src/core/primitives/Graphics.js @@ -0,0 +1,1029 @@ +var core = require('../core'), + GraphicsData = require('./GraphicsData'); + +/** + * 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() { + core.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 {object[]} + * @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 = core.CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {object} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object[]} + * @private + */ + 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 core.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(core.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; + } + } + } +}); + +/** + * 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 core.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 core.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; + + 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 core.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 core.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 core.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 core.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 core.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 core.CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = core.Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + core.CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @method _renderWebGL + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderWebGL = function (renderSession) { + // 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 on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + core.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); + + return; + } + else { + renderSession.spriteBatch.stop(); + renderSession.blendModeManager.setBlendMode(this.blendMode); + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + if (this._filters) { + renderSession.filterManager.pushFilter(this._filterBlock); + } + + // check blend mode + if (this.blendMode !== renderSession.spriteBatch.currentBlendMode) { + renderSession.spriteBatch.currentBlendMode = this.blendMode; + + var blendModeWebGL = blendModesWebGL[renderSession.spriteBatch.currentBlendMode]; + + renderSession.spriteBatch.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); + } + + // check if the webgl graphic needs to be updated + if (this.glDirty) { + this.dirty = true; + this.glDirty = false; + } + + core.WebGLGraphics.renderGraphics(this, renderSession); + + // only render if it has children! + if (this.children.length) { + renderSession.spriteBatch.start(); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderWebGL(renderSession); + } + + renderSession.spriteBatch.stop(); + } + + if (this._filters) { + renderSession.filterManager.popFilter(); + } + + if (this._mask) { + renderSession.maskManager.popMask(this.mask, renderSession); + } + + renderSession.drawCount++; + + renderSession.spriteBatch.start(); + } +}; + +/** + * Renders the object using the Canvas renderer + * + * @method _renderCanvas + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderCanvas = function (renderSession) { + // 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; + + core.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession); + + return; + } + else { + var context = renderSession.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderSession.currentBlendMode) { + renderSession.currentBlendMode = this.blendMode; + context.globalCompositeOperation = blendModesCanvas[renderSession.currentBlendMode]; + } + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + var resolution = renderSession.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + core.CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderCanvas(renderSession); + } + + if (this._mask) { + renderSession.maskManager.popMask(renderSession); + } + } +}; + +/** + * 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 core.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 === core.CONST.SHAPES.RECT || type === core.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 === core.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 === core.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 core.CanvasBuffer(bounds.width, bounds.height); + var texture = core.Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new core.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.. + core.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 === core.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 new file mode 100644 index 0000000..9377ef7 --- /dev/null +++ b/src/core/primitives/GraphicsData.js @@ -0,0 +1,23 @@ +/** + * 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; diff --git a/src/core/primitives/index.js b/src/core/primitives/index.js new file mode 100644 index 0000000..7f02649 --- /dev/null +++ b/src/core/primitives/index.js @@ -0,0 +1,14 @@ +/** + * @file Main export of the PIXI primitives library + * @author Mat Groves + * @copyright 2013-2015 GoodBoyDigital + * @license {@link https://github.com/GoodBoyDigital/pixi.js/blob/master/LICENSE|MIT License} + */ + +/** + * @namespace PIXI + */ +module.exports = { + Graphics: require('./Graphics'), + GraphicsData: require('./GraphicsData') +}; diff --git a/src/core/renderers/canvas/CanvasGraphics.js b/src/core/renderers/canvas/CanvasGraphics.js index 2b1797a..ef7e765 100644 --- a/src/core/renderers/canvas/CanvasGraphics.js +++ b/src/core/renderers/canvas/CanvasGraphics.js @@ -1,4 +1,4 @@ -var Graphics = require('../../../primitives/Graphics'); +var CONST = require('../../const'); /** * A set of functions used by the canvas renderer to draw the primitive graphics data. @@ -30,7 +30,7 @@ context.lineWidth = data.lineWidth; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -61,7 +61,7 @@ context.stroke(); } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { if (data.fillColor || data.fillColor === 0) { context.globalAlpha = data.fillAlpha * worldAlpha; @@ -75,7 +75,7 @@ context.strokeRect(shape.x, shape.y, shape.width, shape.height); } } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); @@ -92,7 +92,7 @@ context.stroke(); } } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas var w = shape.width * 2; @@ -130,7 +130,7 @@ context.stroke(); } } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var rx = shape.x; var ry = shape.y; var width = shape.width; @@ -190,7 +190,7 @@ var data = graphics.graphicsData[i]; var shape = data.shape; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -207,18 +207,18 @@ } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { context.beginPath(); context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); context.closePath(); } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas @@ -245,7 +245,7 @@ context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); context.closePath(); } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var pts = shape.points; var rx = pts[0]; diff --git a/src/core/renderers/webgl/managers/WebGLBlendModeManager.js b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js new file mode 100644 index 0000000..9f2f89a --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js @@ -0,0 +1,37 @@ +var WebGLManager = require('./WebGLManager'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLBlendModeManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; +} + +WebGLBlendModeManager.prototype = Object.create(WebGLManager.prototype); +WebGLBlendModeManager.prototype.constructor = WebGLBlendModeManager; +module.exports = WebGLBlendModeManager; + +/** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as BlendModes.ADD + */ +WebGLBlendModeManager.prototype.setBlendMode = function (blendMode) { + if (this.currentBlendMode === blendMode) { + return false; + } + + this.currentBlendMode = blendMode; + + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; +}; diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js new file mode 100644 index 0000000..176e7cd --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -0,0 +1,446 @@ +var WebGLManager = require('./WebGLManager'), + FilterTexture = require('./FilterTexture'), + PixiShader = require('../shaders/PixiShader'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLFilterManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {any[]} + */ + this.filterStack = []; + + /** + * @member {any[]]} + */ + this.texturePool = []; + + /** + * @member {number} + */ + this.offsetX = 0; + + /** + * @member {number} + */ + this.offsetY = 0; + + // listen for context and update necessary buffers + var self = this; + this.renderer.on('context', function () { + self.texturePool.length = 0; + self.initShaderBuffers(); + }); +} + +WebGLFilterManager.prototype = Object.create(WebGLManager.prototype); +WebGLFilterManager.prototype.constructor = WebGLFilterManager; +module.exports = WebGLFilterManager; + +/** + * @param renderer {WebGLRenderer} + * @param buffer {ArrayBuffer} + */ +WebGLFilterManager.prototype.begin = function (renderer, buffer) { + this.renderer = renderer; + this.defaultShader = renderer.shaderManager.defaultShader; + + this.width = renderer.projection.x * 2; + this.height = -renderer.projection.y * 2; + + this.buffer = buffer; +}; + +/** + * Applies the filter and adds it to the current filter stack. + * + * @param filterBlock {object} the filter that will be pushed to the current filter stack + */ +WebGLFilterManager.prototype.pushFilter = function (filterBlock) { + var gl = this.renderer.gl; + + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds(); + + // filter program + // OPTIMISATION - the first filter is free if its a simple color change? + this.filterStack.push(filterBlock); + + var filter = filterBlock.filterPasses[0]; + + this.offsetX += filterBlock._filterArea.x; + this.offsetY += filterBlock._filterArea.y; + + var texture = this.texturePool.pop(); + if (!texture) { + texture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + else { + texture.resize(this.width, this.height); + } + + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea; + + var padding = filter.padding; + filterArea.x -= padding; + filterArea.y -= padding; + filterArea.width += padding * 2; + filterArea.height += padding * 2; + + // cap filter to screen size.. + if (filterArea.x < 0) { + filterArea.x = 0; + } + + if (filterArea.width > this.width) { + filterArea.width = this.width; + } + + if (filterArea.y < 0) { + filterArea.y = 0; + } + + if (filterArea.height > this.height) { + filterArea.height = this.height; + } + + //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); + + // set view port + gl.viewport(0, 0, filterArea.width, filterArea.height); + + projection.x = filterArea.width/2; + projection.y = -filterArea.height/2; + + offset.x = -filterArea.x; + offset.y = -filterArea.y; + + // update projection + // now restore the regular shader.. + // this.renderer.shaderManager.setShader(this.defaultShader); + //gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); + //gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y); + + gl.colorMask(true, true, true, true); + gl.clearColor(0,0,0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + + filterBlock._glFilterTexture = texture; + +}; + +/** + * Removes the last filter from the filter stack and doesn't return it. + * + */ +WebGLFilterManager.prototype.popFilter = function () { + var gl = this.renderer.gl; + + var filterBlock = this.filterStack.pop(); + var filterArea = filterBlock._filterArea; + var texture = filterBlock._glFilterTexture; + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + if (filterBlock.filterPasses.length > 1) { + gl.viewport(0, 0, filterArea.width, filterArea.height); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = 0; + this.vertexArray[1] = filterArea.height; + + this.vertexArray[2] = filterArea.width; + this.vertexArray[3] = filterArea.height; + + this.vertexArray[4] = 0; + this.vertexArray[5] = 0; + + this.vertexArray[6] = filterArea.width; + this.vertexArray[7] = 0; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + // now set the uvs.. + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + var inputTexture = texture; + var outputTexture = this.texturePool.pop(); + if (!outputTexture) { + outputTexture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + outputTexture.resize(this.width, this.height); + + // need to clear this FBO as it may have some left over elements from a previous filter. + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + gl.clear(gl.COLOR_BUFFER_BIT); + + gl.disable(gl.BLEND); + + for (var i = 0; i < filterBlock.filterPasses.length-1; i++) { + var filterPass = filterBlock.filterPasses[i]; + + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); + + // draw texture.. + //filterPass.applyFilterPass(filterArea.width, filterArea.height); + this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height); + + // swap the textures.. + var temp = inputTexture; + inputTexture = outputTexture; + outputTexture = temp; + } + + gl.enable(gl.BLEND); + + texture = inputTexture; + this.texturePool.push(outputTexture); + } + + var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1]; + + this.offsetX -= filterArea.x; + this.offsetY -= filterArea.y; + + var sizeX = this.width; + var sizeY = this.height; + + var offsetX = 0; + var offsetY = 0; + + var buffer = this.buffer; + + // time to render the filters texture to the previous scene + if (this.filterStack.length === 0) { + gl.colorMask(true, true, true, true);//this.transparent); + } + else { + var currentFilter = this.filterStack[this.filterStack.length-1]; + filterArea = currentFilter._filterArea; + + sizeX = filterArea.width; + sizeY = filterArea.height; + + offsetX = filterArea.x; + offsetY = filterArea.y; + + buffer = currentFilter._glFilterTexture.frameBuffer; + } + + // TODO need to remove these global elements.. + projection.x = sizeX/2; + projection.y = -sizeY/2; + + offset.x = offsetX; + offset.y = offsetY; + + filterArea = filterBlock._filterArea; + + var x = filterArea.x-offsetX; + var y = filterArea.y-offsetY; + + // update the buffers.. + // make sure to flip the y! + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = x; + this.vertexArray[1] = y + filterArea.height; + + this.vertexArray[2] = x + filterArea.width; + this.vertexArray[3] = y + filterArea.height; + + this.vertexArray[4] = x; + this.vertexArray[5] = y; + + this.vertexArray[6] = x + filterArea.width; + this.vertexArray[7] = y; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + gl.viewport(0, 0, sizeX, sizeY); + + // bind the buffer + gl.bindFramebuffer(gl.FRAMEBUFFER, buffer ); + + // set the blend mode! + //gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + // apply! + this.applyFilterPass(filter, filterArea, sizeX, sizeY); + + // now restore the regular shader.. should happen automatically now.. + // this.renderer.shaderManager.setShader(this.defaultShader); + // gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2); + // gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY); + + // return the texture to the pool + this.texturePool.push(texture); + filterBlock._glFilterTexture = null; +}; + + +/** + * Applies the filter to the specified area. + * + * @param filter {AbstractFilter} the filter that needs to be applied + * @param filterArea {Texture} TODO - might need an update + * @param width {number} the horizontal range of the filter + * @param height {number} the vertical range of the filter + */ +WebGLFilterManager.prototype.applyFilterPass = function (filter, filterArea, width, height) { + // use program + var gl = this.renderer.gl; + + var shader = filter.shaders[gl.id]; + + if (!shader) { + shader = new PixiShader(gl); + + shader.fragmentSrc = filter.fragmentSrc; + shader.uniforms = filter.uniforms; + shader.init(); + + filter.shaders[gl.id] = shader; + } + + // set the shader + this.renderer.shaderManager.setShader(shader); + +// gl.useProgram(shader.program); + + gl.uniform2f(shader.projectionVector, width/2, -height/2); + gl.uniform2f(shader.offsetVector, 0,0); + + if (filter.uniforms.dimensions) { + filter.uniforms.dimensions.value[0] = this.width;//width; + filter.uniforms.dimensions.value[1] = this.height;//height; + filter.uniforms.dimensions.value[2] = this.vertexArray[0]; + filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; + } + + shader.syncUniforms(); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + + // draw the filter... + gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); + + this.renderer.drawCount++; +}; + +/** + * Initialises the shader buffers. + * + */ +WebGLFilterManager.prototype.initShaderBuffers = function () { + var gl = this.renderer.gl; + + // create some buffers + this.vertexBuffer = gl.createBuffer(); + this.uvBuffer = gl.createBuffer(); + this.colorBuffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + + // bind and upload the vertexs.. + // keep a reference to the vertexFloatData.. + this.vertexArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW); + + // bind and upload the uv buffer + this.uvArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.uvArray, gl.STATIC_DRAW); + + this.colorArray = new Float32Array([1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.colorArray, gl.STATIC_DRAW); + + // bind and upload the index + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW); + +}; + +/** + * Destroys the filter and removes it from the filter stack. + * + */ +WebGLFilterManager.prototype.destroy = function () { + var gl = this.renderer.gl; + + this.filterStack = null; + + this.offsetX = 0; + this.offsetY = 0; + + // destroy textures + for (var i = 0; i < this.texturePool.length; i++) { + this.texturePool[i].destroy(); + } + + this.texturePool = null; + + //destroy buffers.. + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.uvBuffer); + gl.deleteBuffer(this.colorBuffer); + gl.deleteBuffer(this.indexBuffer); + + this.renderer = null; +}; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js new file mode 100644 index 0000000..9d47b5c --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -0,0 +1,20 @@ +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLManager(renderer) { + /** + * The renderer this manager works for. + * + * @member {WebGLRenderer} + */ + this.renderer = renderer; +} + +WebGLManager.prototype.constructor = WebGLManager; +module.exports = WebGLManager; + +WebGLManager.prototype.destroy = function () { + this.renderer = null; +}; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js new file mode 100644 index 0000000..6d46d85 --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -0,0 +1,41 @@ +var WebGLManager = require('./WebGLManager'), + WebGLGraphics = require('./WebGLGraphics'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLMaskManager(renderer) { + WebGLManager.call(this, renderer); +} + +WebGLMaskManager.prototype = Object.create(WebGLManager.prototype); +WebGLMaskManager.prototype.constructor = WebGLMaskManager; +module.exports = WebGLMaskManager; + +/** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {any[]} + */ +WebGLMaskManager.prototype.pushMask = function (maskData) { + if (maskData.dirty) { + WebGLGraphics.updateGraphics(maskData, this.renderer.gl); + } + + if (!maskData._webGL[this.renderer.gl.id].data.length) { + return; + } + + this.renderer.stencilManager.pushStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); +}; + +/** + * Removes the last filter from the filter stack and doesn't return it. + * + * @param maskData {any[]} + */ +WebGLMaskManager.prototype.popMask = function (maskData) { + this.renderer.stencilManager.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); +}; diff --git a/src/core/const.js b/src/core/const.js index 2803e5b..aea72ec 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -130,5 +130,25 @@ backgroundColor: 0x000000, clearBeforeRender: true, preserveDrawingBuffer: false + }, + + /** + * Constants that identify shapes, mainly to prevent `instanceof` calls. + * + * @static + * @constant + * @property {object} SHAPES + * @property {object} SHAPES.POLY=0 + * @property {object} SHAPES.RECT=1 + * @property {object} SHAPES.CIRC=2 + * @property {object} SHAPES.ELIP=3 + * @property {object} SHAPES.RREC=4 + */ + SHAPES: { + POLY = 0; + RECT = 1; + CIRC = 2; + ELIP = 3; + RREC = 4; } }; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index bfd59cf..7c0e7c6 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -95,7 +95,7 @@ this.filterArea = null; /** - * cached sin rotation and cos rotation + * cached sin rotation * * @member {number} * @private @@ -103,7 +103,7 @@ this._sr = 0; /** - * cached sin rotation and cos rotation + * cached cos rotation * * @member {number} * @private @@ -400,7 +400,7 @@ DisplayObject.prototype.generateTexture = function (resolution, scaleMode, renderer) { var bounds = this.getLocalBounds(); - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); _tempMatrix.tx = -bounds.x; _tempMatrix.ty = -bounds.y; @@ -474,7 +474,7 @@ var bounds = this.getLocalBounds(); if (!this._cachedSprite) { - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0); //, renderer); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0); this._cachedSprite = new Sprite(renderTexture); this._cachedSprite.worldTransform = this.worldTransform; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 3f6a3b6..69cfdc9 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Circle object can be used to specify a hit area for displayObjects @@ -29,10 +30,11 @@ this.radius = radius || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.CIRC in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.CIRC; } Circle.prototype.constructor = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index fc9df4d..4f2d066 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Ellipse object can be used to specify a hit area for displayObjects @@ -36,10 +37,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.ELIP in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.ELIP; } Ellipse.prototype.constructor = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index 5d4a464..ae89148 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -1,4 +1,5 @@ -var Point = require('../Point'); +var Point = require('../Point'), + CONST = require('../../const'); /** * @class @@ -33,6 +34,13 @@ * @member {Point[]} */ this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + */ + this.type = CONST.SHAPES.POLY; } Polygon.prototype.constructor = Polygon; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index c3454b5..16ac62a 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * the Rectangle object is an area defined by its position, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -34,10 +36,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RECT; } Rectangle.prototype.constructor = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0f2abfc..27d286e 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * The Rounded Rectangle object is an area defined by its position and has nice rounded corners, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -41,10 +43,11 @@ this.radius = radius || 20; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RRECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RREC; } RoundedRectangle.prototype.constructor = RoundedRectangle; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js new file mode 100644 index 0000000..42647c5 --- /dev/null +++ b/src/core/primitives/Graphics.js @@ -0,0 +1,1029 @@ +var core = require('../core'), + GraphicsData = require('./GraphicsData'); + +/** + * 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() { + core.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 {object[]} + * @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 = core.CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {object} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object[]} + * @private + */ + 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 core.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(core.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; + } + } + } +}); + +/** + * 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 core.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 core.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; + + 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 core.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 core.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 core.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 core.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 core.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 core.CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = core.Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + core.CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @method _renderWebGL + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderWebGL = function (renderSession) { + // 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 on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + core.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); + + return; + } + else { + renderSession.spriteBatch.stop(); + renderSession.blendModeManager.setBlendMode(this.blendMode); + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + if (this._filters) { + renderSession.filterManager.pushFilter(this._filterBlock); + } + + // check blend mode + if (this.blendMode !== renderSession.spriteBatch.currentBlendMode) { + renderSession.spriteBatch.currentBlendMode = this.blendMode; + + var blendModeWebGL = blendModesWebGL[renderSession.spriteBatch.currentBlendMode]; + + renderSession.spriteBatch.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); + } + + // check if the webgl graphic needs to be updated + if (this.glDirty) { + this.dirty = true; + this.glDirty = false; + } + + core.WebGLGraphics.renderGraphics(this, renderSession); + + // only render if it has children! + if (this.children.length) { + renderSession.spriteBatch.start(); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderWebGL(renderSession); + } + + renderSession.spriteBatch.stop(); + } + + if (this._filters) { + renderSession.filterManager.popFilter(); + } + + if (this._mask) { + renderSession.maskManager.popMask(this.mask, renderSession); + } + + renderSession.drawCount++; + + renderSession.spriteBatch.start(); + } +}; + +/** + * Renders the object using the Canvas renderer + * + * @method _renderCanvas + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderCanvas = function (renderSession) { + // 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; + + core.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession); + + return; + } + else { + var context = renderSession.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderSession.currentBlendMode) { + renderSession.currentBlendMode = this.blendMode; + context.globalCompositeOperation = blendModesCanvas[renderSession.currentBlendMode]; + } + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + var resolution = renderSession.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + core.CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderCanvas(renderSession); + } + + if (this._mask) { + renderSession.maskManager.popMask(renderSession); + } + } +}; + +/** + * 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 core.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 === core.CONST.SHAPES.RECT || type === core.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 === core.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 === core.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 core.CanvasBuffer(bounds.width, bounds.height); + var texture = core.Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new core.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.. + core.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 === core.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 new file mode 100644 index 0000000..9377ef7 --- /dev/null +++ b/src/core/primitives/GraphicsData.js @@ -0,0 +1,23 @@ +/** + * 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; diff --git a/src/core/primitives/index.js b/src/core/primitives/index.js new file mode 100644 index 0000000..7f02649 --- /dev/null +++ b/src/core/primitives/index.js @@ -0,0 +1,14 @@ +/** + * @file Main export of the PIXI primitives library + * @author Mat Groves + * @copyright 2013-2015 GoodBoyDigital + * @license {@link https://github.com/GoodBoyDigital/pixi.js/blob/master/LICENSE|MIT License} + */ + +/** + * @namespace PIXI + */ +module.exports = { + Graphics: require('./Graphics'), + GraphicsData: require('./GraphicsData') +}; diff --git a/src/core/renderers/canvas/CanvasGraphics.js b/src/core/renderers/canvas/CanvasGraphics.js index 2b1797a..ef7e765 100644 --- a/src/core/renderers/canvas/CanvasGraphics.js +++ b/src/core/renderers/canvas/CanvasGraphics.js @@ -1,4 +1,4 @@ -var Graphics = require('../../../primitives/Graphics'); +var CONST = require('../../const'); /** * A set of functions used by the canvas renderer to draw the primitive graphics data. @@ -30,7 +30,7 @@ context.lineWidth = data.lineWidth; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -61,7 +61,7 @@ context.stroke(); } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { if (data.fillColor || data.fillColor === 0) { context.globalAlpha = data.fillAlpha * worldAlpha; @@ -75,7 +75,7 @@ context.strokeRect(shape.x, shape.y, shape.width, shape.height); } } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); @@ -92,7 +92,7 @@ context.stroke(); } } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas var w = shape.width * 2; @@ -130,7 +130,7 @@ context.stroke(); } } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var rx = shape.x; var ry = shape.y; var width = shape.width; @@ -190,7 +190,7 @@ var data = graphics.graphicsData[i]; var shape = data.shape; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -207,18 +207,18 @@ } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { context.beginPath(); context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); context.closePath(); } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas @@ -245,7 +245,7 @@ context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); context.closePath(); } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var pts = shape.points; var rx = pts[0]; diff --git a/src/core/renderers/webgl/managers/WebGLBlendModeManager.js b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js new file mode 100644 index 0000000..9f2f89a --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js @@ -0,0 +1,37 @@ +var WebGLManager = require('./WebGLManager'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLBlendModeManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; +} + +WebGLBlendModeManager.prototype = Object.create(WebGLManager.prototype); +WebGLBlendModeManager.prototype.constructor = WebGLBlendModeManager; +module.exports = WebGLBlendModeManager; + +/** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as BlendModes.ADD + */ +WebGLBlendModeManager.prototype.setBlendMode = function (blendMode) { + if (this.currentBlendMode === blendMode) { + return false; + } + + this.currentBlendMode = blendMode; + + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; +}; diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js new file mode 100644 index 0000000..176e7cd --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -0,0 +1,446 @@ +var WebGLManager = require('./WebGLManager'), + FilterTexture = require('./FilterTexture'), + PixiShader = require('../shaders/PixiShader'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLFilterManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {any[]} + */ + this.filterStack = []; + + /** + * @member {any[]]} + */ + this.texturePool = []; + + /** + * @member {number} + */ + this.offsetX = 0; + + /** + * @member {number} + */ + this.offsetY = 0; + + // listen for context and update necessary buffers + var self = this; + this.renderer.on('context', function () { + self.texturePool.length = 0; + self.initShaderBuffers(); + }); +} + +WebGLFilterManager.prototype = Object.create(WebGLManager.prototype); +WebGLFilterManager.prototype.constructor = WebGLFilterManager; +module.exports = WebGLFilterManager; + +/** + * @param renderer {WebGLRenderer} + * @param buffer {ArrayBuffer} + */ +WebGLFilterManager.prototype.begin = function (renderer, buffer) { + this.renderer = renderer; + this.defaultShader = renderer.shaderManager.defaultShader; + + this.width = renderer.projection.x * 2; + this.height = -renderer.projection.y * 2; + + this.buffer = buffer; +}; + +/** + * Applies the filter and adds it to the current filter stack. + * + * @param filterBlock {object} the filter that will be pushed to the current filter stack + */ +WebGLFilterManager.prototype.pushFilter = function (filterBlock) { + var gl = this.renderer.gl; + + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds(); + + // filter program + // OPTIMISATION - the first filter is free if its a simple color change? + this.filterStack.push(filterBlock); + + var filter = filterBlock.filterPasses[0]; + + this.offsetX += filterBlock._filterArea.x; + this.offsetY += filterBlock._filterArea.y; + + var texture = this.texturePool.pop(); + if (!texture) { + texture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + else { + texture.resize(this.width, this.height); + } + + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea; + + var padding = filter.padding; + filterArea.x -= padding; + filterArea.y -= padding; + filterArea.width += padding * 2; + filterArea.height += padding * 2; + + // cap filter to screen size.. + if (filterArea.x < 0) { + filterArea.x = 0; + } + + if (filterArea.width > this.width) { + filterArea.width = this.width; + } + + if (filterArea.y < 0) { + filterArea.y = 0; + } + + if (filterArea.height > this.height) { + filterArea.height = this.height; + } + + //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); + + // set view port + gl.viewport(0, 0, filterArea.width, filterArea.height); + + projection.x = filterArea.width/2; + projection.y = -filterArea.height/2; + + offset.x = -filterArea.x; + offset.y = -filterArea.y; + + // update projection + // now restore the regular shader.. + // this.renderer.shaderManager.setShader(this.defaultShader); + //gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); + //gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y); + + gl.colorMask(true, true, true, true); + gl.clearColor(0,0,0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + + filterBlock._glFilterTexture = texture; + +}; + +/** + * Removes the last filter from the filter stack and doesn't return it. + * + */ +WebGLFilterManager.prototype.popFilter = function () { + var gl = this.renderer.gl; + + var filterBlock = this.filterStack.pop(); + var filterArea = filterBlock._filterArea; + var texture = filterBlock._glFilterTexture; + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + if (filterBlock.filterPasses.length > 1) { + gl.viewport(0, 0, filterArea.width, filterArea.height); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = 0; + this.vertexArray[1] = filterArea.height; + + this.vertexArray[2] = filterArea.width; + this.vertexArray[3] = filterArea.height; + + this.vertexArray[4] = 0; + this.vertexArray[5] = 0; + + this.vertexArray[6] = filterArea.width; + this.vertexArray[7] = 0; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + // now set the uvs.. + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + var inputTexture = texture; + var outputTexture = this.texturePool.pop(); + if (!outputTexture) { + outputTexture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + outputTexture.resize(this.width, this.height); + + // need to clear this FBO as it may have some left over elements from a previous filter. + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + gl.clear(gl.COLOR_BUFFER_BIT); + + gl.disable(gl.BLEND); + + for (var i = 0; i < filterBlock.filterPasses.length-1; i++) { + var filterPass = filterBlock.filterPasses[i]; + + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); + + // draw texture.. + //filterPass.applyFilterPass(filterArea.width, filterArea.height); + this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height); + + // swap the textures.. + var temp = inputTexture; + inputTexture = outputTexture; + outputTexture = temp; + } + + gl.enable(gl.BLEND); + + texture = inputTexture; + this.texturePool.push(outputTexture); + } + + var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1]; + + this.offsetX -= filterArea.x; + this.offsetY -= filterArea.y; + + var sizeX = this.width; + var sizeY = this.height; + + var offsetX = 0; + var offsetY = 0; + + var buffer = this.buffer; + + // time to render the filters texture to the previous scene + if (this.filterStack.length === 0) { + gl.colorMask(true, true, true, true);//this.transparent); + } + else { + var currentFilter = this.filterStack[this.filterStack.length-1]; + filterArea = currentFilter._filterArea; + + sizeX = filterArea.width; + sizeY = filterArea.height; + + offsetX = filterArea.x; + offsetY = filterArea.y; + + buffer = currentFilter._glFilterTexture.frameBuffer; + } + + // TODO need to remove these global elements.. + projection.x = sizeX/2; + projection.y = -sizeY/2; + + offset.x = offsetX; + offset.y = offsetY; + + filterArea = filterBlock._filterArea; + + var x = filterArea.x-offsetX; + var y = filterArea.y-offsetY; + + // update the buffers.. + // make sure to flip the y! + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = x; + this.vertexArray[1] = y + filterArea.height; + + this.vertexArray[2] = x + filterArea.width; + this.vertexArray[3] = y + filterArea.height; + + this.vertexArray[4] = x; + this.vertexArray[5] = y; + + this.vertexArray[6] = x + filterArea.width; + this.vertexArray[7] = y; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + gl.viewport(0, 0, sizeX, sizeY); + + // bind the buffer + gl.bindFramebuffer(gl.FRAMEBUFFER, buffer ); + + // set the blend mode! + //gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + // apply! + this.applyFilterPass(filter, filterArea, sizeX, sizeY); + + // now restore the regular shader.. should happen automatically now.. + // this.renderer.shaderManager.setShader(this.defaultShader); + // gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2); + // gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY); + + // return the texture to the pool + this.texturePool.push(texture); + filterBlock._glFilterTexture = null; +}; + + +/** + * Applies the filter to the specified area. + * + * @param filter {AbstractFilter} the filter that needs to be applied + * @param filterArea {Texture} TODO - might need an update + * @param width {number} the horizontal range of the filter + * @param height {number} the vertical range of the filter + */ +WebGLFilterManager.prototype.applyFilterPass = function (filter, filterArea, width, height) { + // use program + var gl = this.renderer.gl; + + var shader = filter.shaders[gl.id]; + + if (!shader) { + shader = new PixiShader(gl); + + shader.fragmentSrc = filter.fragmentSrc; + shader.uniforms = filter.uniforms; + shader.init(); + + filter.shaders[gl.id] = shader; + } + + // set the shader + this.renderer.shaderManager.setShader(shader); + +// gl.useProgram(shader.program); + + gl.uniform2f(shader.projectionVector, width/2, -height/2); + gl.uniform2f(shader.offsetVector, 0,0); + + if (filter.uniforms.dimensions) { + filter.uniforms.dimensions.value[0] = this.width;//width; + filter.uniforms.dimensions.value[1] = this.height;//height; + filter.uniforms.dimensions.value[2] = this.vertexArray[0]; + filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; + } + + shader.syncUniforms(); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + + // draw the filter... + gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); + + this.renderer.drawCount++; +}; + +/** + * Initialises the shader buffers. + * + */ +WebGLFilterManager.prototype.initShaderBuffers = function () { + var gl = this.renderer.gl; + + // create some buffers + this.vertexBuffer = gl.createBuffer(); + this.uvBuffer = gl.createBuffer(); + this.colorBuffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + + // bind and upload the vertexs.. + // keep a reference to the vertexFloatData.. + this.vertexArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW); + + // bind and upload the uv buffer + this.uvArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.uvArray, gl.STATIC_DRAW); + + this.colorArray = new Float32Array([1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.colorArray, gl.STATIC_DRAW); + + // bind and upload the index + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW); + +}; + +/** + * Destroys the filter and removes it from the filter stack. + * + */ +WebGLFilterManager.prototype.destroy = function () { + var gl = this.renderer.gl; + + this.filterStack = null; + + this.offsetX = 0; + this.offsetY = 0; + + // destroy textures + for (var i = 0; i < this.texturePool.length; i++) { + this.texturePool[i].destroy(); + } + + this.texturePool = null; + + //destroy buffers.. + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.uvBuffer); + gl.deleteBuffer(this.colorBuffer); + gl.deleteBuffer(this.indexBuffer); + + this.renderer = null; +}; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js new file mode 100644 index 0000000..9d47b5c --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -0,0 +1,20 @@ +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLManager(renderer) { + /** + * The renderer this manager works for. + * + * @member {WebGLRenderer} + */ + this.renderer = renderer; +} + +WebGLManager.prototype.constructor = WebGLManager; +module.exports = WebGLManager; + +WebGLManager.prototype.destroy = function () { + this.renderer = null; +}; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js new file mode 100644 index 0000000..6d46d85 --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -0,0 +1,41 @@ +var WebGLManager = require('./WebGLManager'), + WebGLGraphics = require('./WebGLGraphics'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLMaskManager(renderer) { + WebGLManager.call(this, renderer); +} + +WebGLMaskManager.prototype = Object.create(WebGLManager.prototype); +WebGLMaskManager.prototype.constructor = WebGLMaskManager; +module.exports = WebGLMaskManager; + +/** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {any[]} + */ +WebGLMaskManager.prototype.pushMask = function (maskData) { + if (maskData.dirty) { + WebGLGraphics.updateGraphics(maskData, this.renderer.gl); + } + + if (!maskData._webGL[this.renderer.gl.id].data.length) { + return; + } + + this.renderer.stencilManager.pushStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); +}; + +/** + * Removes the last filter from the filter stack and doesn't return it. + * + * @param maskData {any[]} + */ +WebGLMaskManager.prototype.popMask = function (maskData) { + this.renderer.stencilManager.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 new file mode 100644 index 0000000..d02d95d --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -0,0 +1,172 @@ +var WebGLManager = require('./WebGLManager'), + PrimitiveShader = require('../shaders/PrimitiveShader'), + ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), + PixiShader = require('../shaders/PixiShader'), + PixiFastShader = require('../shaders/PixiFastShader'), + StripShader = require('../shaders/StripShader'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLShaderManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {number} + */ + this.maxAttibs = 10; + + /** + * @member {any[]} + */ + this.attribState = []; + + /** + * @member {any[]} + */ + this.tempAttribState = []; + + for (var i = 0; i < this.maxAttibs; i++) { + this.attribState[i] = false; + } + + /** + * @member {any[]} + */ + this.stack = []; + + /** + * @member {number} + * @private + */ + this._currentId = -1; + + /** + * @member {Shader} + * @private + */ + 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; + + // listen for context and update necessary shaders + var self = this; + this.renderer.on('context', function (gl) { + // 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 PixiShader(gl); + + // this shader is used for the fast sprite rendering + self.fastShader = new PixiFastShader(gl); + + // the next one is used for rendering triangle strips + self.stripShader = new StripShader(gl); + + self.setShader(self.defaultShader); + }); +} + +WebGLShaderManager.prototype = Object.create(WebGLManager.prototype); +WebGLShaderManager.prototype.constructor = WebGLShaderManager; +module.exports = WebGLShaderManager; + +/** + * Takes the attributes given in parameters. + * + * @param attribs {Array} attribs + */ +WebGLShaderManager.prototype.setAttribs = function (attribs) { + // reset temp state + var i; + + for (i = 0; i < this.tempAttribState.length; i++) { + this.tempAttribState[i] = false; + } + + // set the new attribs + for (var a in attribs) { + this.tempAttribState[attribs[a]] = true; + } + + var gl = this.renderer.gl; + + for (i = 0; i < this.attribState.length; i++) { + if (this.attribState[i] !== this.tempAttribState[i]) { + this.attribState[i] = this.tempAttribState[i]; + + if (this.attribState[i]) { + gl.enableVertexAttribArray(i); + } + else { + gl.disableVertexAttribArray(i); + } + } + } +}; + +/** + * Sets the current shader. + * + * @param shader {Any} + */ +WebGLShaderManager.prototype.setShader = function (shader) { + if (this._currentId === shader.uuid) { + return false; + } + + this._currentId = shader.uuid; + + this.currentShader = shader; + + this.renderer.gl.useProgram(shader.program); + this.setAttribs(shader.attributes); + + return true; +}; + +/** + * Destroys this object. + * + */ +WebGLShaderManager.prototype.destroy = function () { + 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/const.js b/src/core/const.js index 2803e5b..aea72ec 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -130,5 +130,25 @@ backgroundColor: 0x000000, clearBeforeRender: true, preserveDrawingBuffer: false + }, + + /** + * Constants that identify shapes, mainly to prevent `instanceof` calls. + * + * @static + * @constant + * @property {object} SHAPES + * @property {object} SHAPES.POLY=0 + * @property {object} SHAPES.RECT=1 + * @property {object} SHAPES.CIRC=2 + * @property {object} SHAPES.ELIP=3 + * @property {object} SHAPES.RREC=4 + */ + SHAPES: { + POLY = 0; + RECT = 1; + CIRC = 2; + ELIP = 3; + RREC = 4; } }; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index bfd59cf..7c0e7c6 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -95,7 +95,7 @@ this.filterArea = null; /** - * cached sin rotation and cos rotation + * cached sin rotation * * @member {number} * @private @@ -103,7 +103,7 @@ this._sr = 0; /** - * cached sin rotation and cos rotation + * cached cos rotation * * @member {number} * @private @@ -400,7 +400,7 @@ DisplayObject.prototype.generateTexture = function (resolution, scaleMode, renderer) { var bounds = this.getLocalBounds(); - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); _tempMatrix.tx = -bounds.x; _tempMatrix.ty = -bounds.y; @@ -474,7 +474,7 @@ var bounds = this.getLocalBounds(); if (!this._cachedSprite) { - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0); //, renderer); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0); this._cachedSprite = new Sprite(renderTexture); this._cachedSprite.worldTransform = this.worldTransform; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 3f6a3b6..69cfdc9 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Circle object can be used to specify a hit area for displayObjects @@ -29,10 +30,11 @@ this.radius = radius || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.CIRC in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.CIRC; } Circle.prototype.constructor = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index fc9df4d..4f2d066 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Ellipse object can be used to specify a hit area for displayObjects @@ -36,10 +37,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.ELIP in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.ELIP; } Ellipse.prototype.constructor = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index 5d4a464..ae89148 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -1,4 +1,5 @@ -var Point = require('../Point'); +var Point = require('../Point'), + CONST = require('../../const'); /** * @class @@ -33,6 +34,13 @@ * @member {Point[]} */ this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + */ + this.type = CONST.SHAPES.POLY; } Polygon.prototype.constructor = Polygon; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index c3454b5..16ac62a 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * the Rectangle object is an area defined by its position, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -34,10 +36,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RECT; } Rectangle.prototype.constructor = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0f2abfc..27d286e 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * The Rounded Rectangle object is an area defined by its position and has nice rounded corners, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -41,10 +43,11 @@ this.radius = radius || 20; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RRECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RREC; } RoundedRectangle.prototype.constructor = RoundedRectangle; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js new file mode 100644 index 0000000..42647c5 --- /dev/null +++ b/src/core/primitives/Graphics.js @@ -0,0 +1,1029 @@ +var core = require('../core'), + GraphicsData = require('./GraphicsData'); + +/** + * 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() { + core.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 {object[]} + * @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 = core.CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {object} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object[]} + * @private + */ + 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 core.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(core.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; + } + } + } +}); + +/** + * 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 core.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 core.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; + + 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 core.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 core.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 core.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 core.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 core.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 core.CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = core.Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + core.CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @method _renderWebGL + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderWebGL = function (renderSession) { + // 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 on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + core.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); + + return; + } + else { + renderSession.spriteBatch.stop(); + renderSession.blendModeManager.setBlendMode(this.blendMode); + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + if (this._filters) { + renderSession.filterManager.pushFilter(this._filterBlock); + } + + // check blend mode + if (this.blendMode !== renderSession.spriteBatch.currentBlendMode) { + renderSession.spriteBatch.currentBlendMode = this.blendMode; + + var blendModeWebGL = blendModesWebGL[renderSession.spriteBatch.currentBlendMode]; + + renderSession.spriteBatch.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); + } + + // check if the webgl graphic needs to be updated + if (this.glDirty) { + this.dirty = true; + this.glDirty = false; + } + + core.WebGLGraphics.renderGraphics(this, renderSession); + + // only render if it has children! + if (this.children.length) { + renderSession.spriteBatch.start(); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderWebGL(renderSession); + } + + renderSession.spriteBatch.stop(); + } + + if (this._filters) { + renderSession.filterManager.popFilter(); + } + + if (this._mask) { + renderSession.maskManager.popMask(this.mask, renderSession); + } + + renderSession.drawCount++; + + renderSession.spriteBatch.start(); + } +}; + +/** + * Renders the object using the Canvas renderer + * + * @method _renderCanvas + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderCanvas = function (renderSession) { + // 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; + + core.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession); + + return; + } + else { + var context = renderSession.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderSession.currentBlendMode) { + renderSession.currentBlendMode = this.blendMode; + context.globalCompositeOperation = blendModesCanvas[renderSession.currentBlendMode]; + } + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + var resolution = renderSession.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + core.CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderCanvas(renderSession); + } + + if (this._mask) { + renderSession.maskManager.popMask(renderSession); + } + } +}; + +/** + * 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 core.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 === core.CONST.SHAPES.RECT || type === core.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 === core.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 === core.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 core.CanvasBuffer(bounds.width, bounds.height); + var texture = core.Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new core.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.. + core.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 === core.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 new file mode 100644 index 0000000..9377ef7 --- /dev/null +++ b/src/core/primitives/GraphicsData.js @@ -0,0 +1,23 @@ +/** + * 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; diff --git a/src/core/primitives/index.js b/src/core/primitives/index.js new file mode 100644 index 0000000..7f02649 --- /dev/null +++ b/src/core/primitives/index.js @@ -0,0 +1,14 @@ +/** + * @file Main export of the PIXI primitives library + * @author Mat Groves + * @copyright 2013-2015 GoodBoyDigital + * @license {@link https://github.com/GoodBoyDigital/pixi.js/blob/master/LICENSE|MIT License} + */ + +/** + * @namespace PIXI + */ +module.exports = { + Graphics: require('./Graphics'), + GraphicsData: require('./GraphicsData') +}; diff --git a/src/core/renderers/canvas/CanvasGraphics.js b/src/core/renderers/canvas/CanvasGraphics.js index 2b1797a..ef7e765 100644 --- a/src/core/renderers/canvas/CanvasGraphics.js +++ b/src/core/renderers/canvas/CanvasGraphics.js @@ -1,4 +1,4 @@ -var Graphics = require('../../../primitives/Graphics'); +var CONST = require('../../const'); /** * A set of functions used by the canvas renderer to draw the primitive graphics data. @@ -30,7 +30,7 @@ context.lineWidth = data.lineWidth; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -61,7 +61,7 @@ context.stroke(); } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { if (data.fillColor || data.fillColor === 0) { context.globalAlpha = data.fillAlpha * worldAlpha; @@ -75,7 +75,7 @@ context.strokeRect(shape.x, shape.y, shape.width, shape.height); } } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); @@ -92,7 +92,7 @@ context.stroke(); } } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas var w = shape.width * 2; @@ -130,7 +130,7 @@ context.stroke(); } } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var rx = shape.x; var ry = shape.y; var width = shape.width; @@ -190,7 +190,7 @@ var data = graphics.graphicsData[i]; var shape = data.shape; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -207,18 +207,18 @@ } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { context.beginPath(); context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); context.closePath(); } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas @@ -245,7 +245,7 @@ context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); context.closePath(); } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var pts = shape.points; var rx = pts[0]; diff --git a/src/core/renderers/webgl/managers/WebGLBlendModeManager.js b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js new file mode 100644 index 0000000..9f2f89a --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js @@ -0,0 +1,37 @@ +var WebGLManager = require('./WebGLManager'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLBlendModeManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; +} + +WebGLBlendModeManager.prototype = Object.create(WebGLManager.prototype); +WebGLBlendModeManager.prototype.constructor = WebGLBlendModeManager; +module.exports = WebGLBlendModeManager; + +/** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as BlendModes.ADD + */ +WebGLBlendModeManager.prototype.setBlendMode = function (blendMode) { + if (this.currentBlendMode === blendMode) { + return false; + } + + this.currentBlendMode = blendMode; + + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; +}; diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js new file mode 100644 index 0000000..176e7cd --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -0,0 +1,446 @@ +var WebGLManager = require('./WebGLManager'), + FilterTexture = require('./FilterTexture'), + PixiShader = require('../shaders/PixiShader'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLFilterManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {any[]} + */ + this.filterStack = []; + + /** + * @member {any[]]} + */ + this.texturePool = []; + + /** + * @member {number} + */ + this.offsetX = 0; + + /** + * @member {number} + */ + this.offsetY = 0; + + // listen for context and update necessary buffers + var self = this; + this.renderer.on('context', function () { + self.texturePool.length = 0; + self.initShaderBuffers(); + }); +} + +WebGLFilterManager.prototype = Object.create(WebGLManager.prototype); +WebGLFilterManager.prototype.constructor = WebGLFilterManager; +module.exports = WebGLFilterManager; + +/** + * @param renderer {WebGLRenderer} + * @param buffer {ArrayBuffer} + */ +WebGLFilterManager.prototype.begin = function (renderer, buffer) { + this.renderer = renderer; + this.defaultShader = renderer.shaderManager.defaultShader; + + this.width = renderer.projection.x * 2; + this.height = -renderer.projection.y * 2; + + this.buffer = buffer; +}; + +/** + * Applies the filter and adds it to the current filter stack. + * + * @param filterBlock {object} the filter that will be pushed to the current filter stack + */ +WebGLFilterManager.prototype.pushFilter = function (filterBlock) { + var gl = this.renderer.gl; + + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds(); + + // filter program + // OPTIMISATION - the first filter is free if its a simple color change? + this.filterStack.push(filterBlock); + + var filter = filterBlock.filterPasses[0]; + + this.offsetX += filterBlock._filterArea.x; + this.offsetY += filterBlock._filterArea.y; + + var texture = this.texturePool.pop(); + if (!texture) { + texture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + else { + texture.resize(this.width, this.height); + } + + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea; + + var padding = filter.padding; + filterArea.x -= padding; + filterArea.y -= padding; + filterArea.width += padding * 2; + filterArea.height += padding * 2; + + // cap filter to screen size.. + if (filterArea.x < 0) { + filterArea.x = 0; + } + + if (filterArea.width > this.width) { + filterArea.width = this.width; + } + + if (filterArea.y < 0) { + filterArea.y = 0; + } + + if (filterArea.height > this.height) { + filterArea.height = this.height; + } + + //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); + + // set view port + gl.viewport(0, 0, filterArea.width, filterArea.height); + + projection.x = filterArea.width/2; + projection.y = -filterArea.height/2; + + offset.x = -filterArea.x; + offset.y = -filterArea.y; + + // update projection + // now restore the regular shader.. + // this.renderer.shaderManager.setShader(this.defaultShader); + //gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); + //gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y); + + gl.colorMask(true, true, true, true); + gl.clearColor(0,0,0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + + filterBlock._glFilterTexture = texture; + +}; + +/** + * Removes the last filter from the filter stack and doesn't return it. + * + */ +WebGLFilterManager.prototype.popFilter = function () { + var gl = this.renderer.gl; + + var filterBlock = this.filterStack.pop(); + var filterArea = filterBlock._filterArea; + var texture = filterBlock._glFilterTexture; + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + if (filterBlock.filterPasses.length > 1) { + gl.viewport(0, 0, filterArea.width, filterArea.height); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = 0; + this.vertexArray[1] = filterArea.height; + + this.vertexArray[2] = filterArea.width; + this.vertexArray[3] = filterArea.height; + + this.vertexArray[4] = 0; + this.vertexArray[5] = 0; + + this.vertexArray[6] = filterArea.width; + this.vertexArray[7] = 0; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + // now set the uvs.. + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + var inputTexture = texture; + var outputTexture = this.texturePool.pop(); + if (!outputTexture) { + outputTexture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + outputTexture.resize(this.width, this.height); + + // need to clear this FBO as it may have some left over elements from a previous filter. + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + gl.clear(gl.COLOR_BUFFER_BIT); + + gl.disable(gl.BLEND); + + for (var i = 0; i < filterBlock.filterPasses.length-1; i++) { + var filterPass = filterBlock.filterPasses[i]; + + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); + + // draw texture.. + //filterPass.applyFilterPass(filterArea.width, filterArea.height); + this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height); + + // swap the textures.. + var temp = inputTexture; + inputTexture = outputTexture; + outputTexture = temp; + } + + gl.enable(gl.BLEND); + + texture = inputTexture; + this.texturePool.push(outputTexture); + } + + var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1]; + + this.offsetX -= filterArea.x; + this.offsetY -= filterArea.y; + + var sizeX = this.width; + var sizeY = this.height; + + var offsetX = 0; + var offsetY = 0; + + var buffer = this.buffer; + + // time to render the filters texture to the previous scene + if (this.filterStack.length === 0) { + gl.colorMask(true, true, true, true);//this.transparent); + } + else { + var currentFilter = this.filterStack[this.filterStack.length-1]; + filterArea = currentFilter._filterArea; + + sizeX = filterArea.width; + sizeY = filterArea.height; + + offsetX = filterArea.x; + offsetY = filterArea.y; + + buffer = currentFilter._glFilterTexture.frameBuffer; + } + + // TODO need to remove these global elements.. + projection.x = sizeX/2; + projection.y = -sizeY/2; + + offset.x = offsetX; + offset.y = offsetY; + + filterArea = filterBlock._filterArea; + + var x = filterArea.x-offsetX; + var y = filterArea.y-offsetY; + + // update the buffers.. + // make sure to flip the y! + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = x; + this.vertexArray[1] = y + filterArea.height; + + this.vertexArray[2] = x + filterArea.width; + this.vertexArray[3] = y + filterArea.height; + + this.vertexArray[4] = x; + this.vertexArray[5] = y; + + this.vertexArray[6] = x + filterArea.width; + this.vertexArray[7] = y; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + gl.viewport(0, 0, sizeX, sizeY); + + // bind the buffer + gl.bindFramebuffer(gl.FRAMEBUFFER, buffer ); + + // set the blend mode! + //gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + // apply! + this.applyFilterPass(filter, filterArea, sizeX, sizeY); + + // now restore the regular shader.. should happen automatically now.. + // this.renderer.shaderManager.setShader(this.defaultShader); + // gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2); + // gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY); + + // return the texture to the pool + this.texturePool.push(texture); + filterBlock._glFilterTexture = null; +}; + + +/** + * Applies the filter to the specified area. + * + * @param filter {AbstractFilter} the filter that needs to be applied + * @param filterArea {Texture} TODO - might need an update + * @param width {number} the horizontal range of the filter + * @param height {number} the vertical range of the filter + */ +WebGLFilterManager.prototype.applyFilterPass = function (filter, filterArea, width, height) { + // use program + var gl = this.renderer.gl; + + var shader = filter.shaders[gl.id]; + + if (!shader) { + shader = new PixiShader(gl); + + shader.fragmentSrc = filter.fragmentSrc; + shader.uniforms = filter.uniforms; + shader.init(); + + filter.shaders[gl.id] = shader; + } + + // set the shader + this.renderer.shaderManager.setShader(shader); + +// gl.useProgram(shader.program); + + gl.uniform2f(shader.projectionVector, width/2, -height/2); + gl.uniform2f(shader.offsetVector, 0,0); + + if (filter.uniforms.dimensions) { + filter.uniforms.dimensions.value[0] = this.width;//width; + filter.uniforms.dimensions.value[1] = this.height;//height; + filter.uniforms.dimensions.value[2] = this.vertexArray[0]; + filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; + } + + shader.syncUniforms(); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + + // draw the filter... + gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); + + this.renderer.drawCount++; +}; + +/** + * Initialises the shader buffers. + * + */ +WebGLFilterManager.prototype.initShaderBuffers = function () { + var gl = this.renderer.gl; + + // create some buffers + this.vertexBuffer = gl.createBuffer(); + this.uvBuffer = gl.createBuffer(); + this.colorBuffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + + // bind and upload the vertexs.. + // keep a reference to the vertexFloatData.. + this.vertexArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW); + + // bind and upload the uv buffer + this.uvArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.uvArray, gl.STATIC_DRAW); + + this.colorArray = new Float32Array([1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.colorArray, gl.STATIC_DRAW); + + // bind and upload the index + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW); + +}; + +/** + * Destroys the filter and removes it from the filter stack. + * + */ +WebGLFilterManager.prototype.destroy = function () { + var gl = this.renderer.gl; + + this.filterStack = null; + + this.offsetX = 0; + this.offsetY = 0; + + // destroy textures + for (var i = 0; i < this.texturePool.length; i++) { + this.texturePool[i].destroy(); + } + + this.texturePool = null; + + //destroy buffers.. + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.uvBuffer); + gl.deleteBuffer(this.colorBuffer); + gl.deleteBuffer(this.indexBuffer); + + this.renderer = null; +}; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js new file mode 100644 index 0000000..9d47b5c --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -0,0 +1,20 @@ +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLManager(renderer) { + /** + * The renderer this manager works for. + * + * @member {WebGLRenderer} + */ + this.renderer = renderer; +} + +WebGLManager.prototype.constructor = WebGLManager; +module.exports = WebGLManager; + +WebGLManager.prototype.destroy = function () { + this.renderer = null; +}; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js new file mode 100644 index 0000000..6d46d85 --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -0,0 +1,41 @@ +var WebGLManager = require('./WebGLManager'), + WebGLGraphics = require('./WebGLGraphics'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLMaskManager(renderer) { + WebGLManager.call(this, renderer); +} + +WebGLMaskManager.prototype = Object.create(WebGLManager.prototype); +WebGLMaskManager.prototype.constructor = WebGLMaskManager; +module.exports = WebGLMaskManager; + +/** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {any[]} + */ +WebGLMaskManager.prototype.pushMask = function (maskData) { + if (maskData.dirty) { + WebGLGraphics.updateGraphics(maskData, this.renderer.gl); + } + + if (!maskData._webGL[this.renderer.gl.id].data.length) { + return; + } + + this.renderer.stencilManager.pushStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); +}; + +/** + * Removes the last filter from the filter stack and doesn't return it. + * + * @param maskData {any[]} + */ +WebGLMaskManager.prototype.popMask = function (maskData) { + this.renderer.stencilManager.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 new file mode 100644 index 0000000..d02d95d --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -0,0 +1,172 @@ +var WebGLManager = require('./WebGLManager'), + PrimitiveShader = require('../shaders/PrimitiveShader'), + ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), + PixiShader = require('../shaders/PixiShader'), + PixiFastShader = require('../shaders/PixiFastShader'), + StripShader = require('../shaders/StripShader'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLShaderManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {number} + */ + this.maxAttibs = 10; + + /** + * @member {any[]} + */ + this.attribState = []; + + /** + * @member {any[]} + */ + this.tempAttribState = []; + + for (var i = 0; i < this.maxAttibs; i++) { + this.attribState[i] = false; + } + + /** + * @member {any[]} + */ + this.stack = []; + + /** + * @member {number} + * @private + */ + this._currentId = -1; + + /** + * @member {Shader} + * @private + */ + 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; + + // listen for context and update necessary shaders + var self = this; + this.renderer.on('context', function (gl) { + // 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 PixiShader(gl); + + // this shader is used for the fast sprite rendering + self.fastShader = new PixiFastShader(gl); + + // the next one is used for rendering triangle strips + self.stripShader = new StripShader(gl); + + self.setShader(self.defaultShader); + }); +} + +WebGLShaderManager.prototype = Object.create(WebGLManager.prototype); +WebGLShaderManager.prototype.constructor = WebGLShaderManager; +module.exports = WebGLShaderManager; + +/** + * Takes the attributes given in parameters. + * + * @param attribs {Array} attribs + */ +WebGLShaderManager.prototype.setAttribs = function (attribs) { + // reset temp state + var i; + + for (i = 0; i < this.tempAttribState.length; i++) { + this.tempAttribState[i] = false; + } + + // set the new attribs + for (var a in attribs) { + this.tempAttribState[attribs[a]] = true; + } + + var gl = this.renderer.gl; + + for (i = 0; i < this.attribState.length; i++) { + if (this.attribState[i] !== this.tempAttribState[i]) { + this.attribState[i] = this.tempAttribState[i]; + + if (this.attribState[i]) { + gl.enableVertexAttribArray(i); + } + else { + gl.disableVertexAttribArray(i); + } + } + } +}; + +/** + * Sets the current shader. + * + * @param shader {Any} + */ +WebGLShaderManager.prototype.setShader = function (shader) { + if (this._currentId === shader.uuid) { + return false; + } + + this._currentId = shader.uuid; + + this.currentShader = shader; + + this.renderer.gl.useProgram(shader.program); + this.setAttribs(shader.attributes); + + return true; +}; + +/** + * Destroys this object. + * + */ +WebGLShaderManager.prototype.destroy = function () { + 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/managers/WebGLStencilManager.js b/src/core/renderers/webgl/managers/WebGLStencilManager.js new file mode 100644 index 0000000..0bedae6 --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLStencilManager.js @@ -0,0 +1,256 @@ +var WebGLManager = require('./WebGLManager'), + utils = require('../../../utils'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLStencilManager(renderer) { + WebGLManager.call(this, renderer); + + this.stencilStack = []; + this.reverse = true; + this.count = 0; +} + +WebGLStencilManager.prototype = Object.create(WebGLManager.prototype); +WebGLStencilManager.prototype.constructor = WebGLStencilManager; +module.exports = WebGLStencilManager; + +/** + * Applies the Mask and adds it to the current filter stack. + * + * @param graphics {Graphics} + * @param webGLData {any[]} + */ +WebGLStencilManager.prototype.pushStencil = function (graphics, webGLData) { + var gl = this.renderer.gl; + + this.bindGraphics(graphics, webGLData, this.renderer); + + if (this.stencilStack.length === 0) { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + this.reverse = true; + this.count = 0; + } + + this.stencilStack.push(webGLData); + + var level = this.count; + + gl.colorMask(false, false, false, false); + + gl.stencilFunc(gl.ALWAYS,0,0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); + + // draw the triangle strip! + + if (webGLData.mode === 1) { + gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + + // draw a quad to increment.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + } + + this.reverse = !this.reverse; + } + else { + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + } + } + + gl.colorMask(true, true, true, true); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + + this.count++; +}; + +/** + * TODO this does not belong here! + * + * @param graphics {Graphics} + * @param webGLData {Array} + */ +WebGLStencilManager.prototype.bindGraphics = function (graphics, webGLData) { + //if (this._currentGraphics === graphics)return; + this._currentGraphics = graphics; + + var gl = this.renderer.gl; + + // bind the graphics object.. + var projection = this.renderer.projection, + offset = this.renderer.offset, + shader;// = this.renderer.shaderManager.primitiveShader; + + if (webGLData.mode === 1) { + shader = this.renderer.shaderManager.complexPrimitiveShader; + + this.renderer.shaderManager.setShader(shader); + + gl.uniform1f(shader.flipY, this.renderer.flipY); + + gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); + + gl.uniform2f(shader.projectionVector, projection.x, -projection.y); + gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); + + gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); + gl.uniform3fv(shader.color, webGLData.color); + + gl.uniform1f(shader.alpha, graphics.worldAlpha * webGLData.alpha); + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 2, 0); + + + // now do the rest.. + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + } + else { + //this.renderer.shaderManager.activatePrimitiveShader(); + shader = this.renderer.shaderManager.primitiveShader; + this.renderer.shaderManager.setShader( shader ); + + gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); + + gl.uniform1f(shader.flipY, this.renderer.flipY); + gl.uniform2f(shader.projectionVector, projection.x, -projection.y); + gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); + + gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); + + gl.uniform1f(shader.alpha, graphics.worldAlpha); + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); + gl.vertexAttribPointer(shader.colorAttribute, 4, gl.FLOAT, false,4 * 6, 2 * 4); + + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + } +}; + +/** + * @param graphics {Graphics} + * @param webGLData {Array} + */ +WebGLStencilManager.prototype.popStencil = function (graphics, webGLData) { + var gl = this.renderer.gl; + + this.stencilStack.pop(); + + this.count--; + + if (this.stencilStack.length === 0) { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + + } + else { + + var level = this.count; + + this.bindGraphics(graphics, webGLData, this.renderer); + + gl.colorMask(false, false, false, false); + + if (webGLData.mode === 1) { + this.reverse = !this.reverse; + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + + // draw a quad to increment.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + gl.stencilFunc(gl.ALWAYS,0,0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); + + // draw the triangle strip! + gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + } + + } + else { + // console.log("<<>>") + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + } + } + + gl.colorMask(true, true, true, true); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + + + } +}; + +/** + * Destroys the mask stack. + * + */ +WebGLStencilManager.prototype.destroy = function () { + this.renderer = null; + this.stencilStack = null; +}; diff --git a/src/core/const.js b/src/core/const.js index 2803e5b..aea72ec 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -130,5 +130,25 @@ backgroundColor: 0x000000, clearBeforeRender: true, preserveDrawingBuffer: false + }, + + /** + * Constants that identify shapes, mainly to prevent `instanceof` calls. + * + * @static + * @constant + * @property {object} SHAPES + * @property {object} SHAPES.POLY=0 + * @property {object} SHAPES.RECT=1 + * @property {object} SHAPES.CIRC=2 + * @property {object} SHAPES.ELIP=3 + * @property {object} SHAPES.RREC=4 + */ + SHAPES: { + POLY = 0; + RECT = 1; + CIRC = 2; + ELIP = 3; + RREC = 4; } }; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index bfd59cf..7c0e7c6 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -95,7 +95,7 @@ this.filterArea = null; /** - * cached sin rotation and cos rotation + * cached sin rotation * * @member {number} * @private @@ -103,7 +103,7 @@ this._sr = 0; /** - * cached sin rotation and cos rotation + * cached cos rotation * * @member {number} * @private @@ -400,7 +400,7 @@ DisplayObject.prototype.generateTexture = function (resolution, scaleMode, renderer) { var bounds = this.getLocalBounds(); - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); _tempMatrix.tx = -bounds.x; _tempMatrix.ty = -bounds.y; @@ -474,7 +474,7 @@ var bounds = this.getLocalBounds(); if (!this._cachedSprite) { - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0); //, renderer); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0); this._cachedSprite = new Sprite(renderTexture); this._cachedSprite.worldTransform = this.worldTransform; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 3f6a3b6..69cfdc9 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Circle object can be used to specify a hit area for displayObjects @@ -29,10 +30,11 @@ this.radius = radius || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.CIRC in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.CIRC; } Circle.prototype.constructor = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index fc9df4d..4f2d066 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Ellipse object can be used to specify a hit area for displayObjects @@ -36,10 +37,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.ELIP in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.ELIP; } Ellipse.prototype.constructor = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index 5d4a464..ae89148 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -1,4 +1,5 @@ -var Point = require('../Point'); +var Point = require('../Point'), + CONST = require('../../const'); /** * @class @@ -33,6 +34,13 @@ * @member {Point[]} */ this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + */ + this.type = CONST.SHAPES.POLY; } Polygon.prototype.constructor = Polygon; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index c3454b5..16ac62a 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * the Rectangle object is an area defined by its position, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -34,10 +36,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RECT; } Rectangle.prototype.constructor = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0f2abfc..27d286e 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * The Rounded Rectangle object is an area defined by its position and has nice rounded corners, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -41,10 +43,11 @@ this.radius = radius || 20; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RRECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RREC; } RoundedRectangle.prototype.constructor = RoundedRectangle; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js new file mode 100644 index 0000000..42647c5 --- /dev/null +++ b/src/core/primitives/Graphics.js @@ -0,0 +1,1029 @@ +var core = require('../core'), + GraphicsData = require('./GraphicsData'); + +/** + * 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() { + core.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 {object[]} + * @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 = core.CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {object} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object[]} + * @private + */ + 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 core.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(core.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; + } + } + } +}); + +/** + * 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 core.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 core.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; + + 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 core.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 core.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 core.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 core.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 core.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 core.CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = core.Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + core.CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @method _renderWebGL + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderWebGL = function (renderSession) { + // 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 on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + core.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); + + return; + } + else { + renderSession.spriteBatch.stop(); + renderSession.blendModeManager.setBlendMode(this.blendMode); + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + if (this._filters) { + renderSession.filterManager.pushFilter(this._filterBlock); + } + + // check blend mode + if (this.blendMode !== renderSession.spriteBatch.currentBlendMode) { + renderSession.spriteBatch.currentBlendMode = this.blendMode; + + var blendModeWebGL = blendModesWebGL[renderSession.spriteBatch.currentBlendMode]; + + renderSession.spriteBatch.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); + } + + // check if the webgl graphic needs to be updated + if (this.glDirty) { + this.dirty = true; + this.glDirty = false; + } + + core.WebGLGraphics.renderGraphics(this, renderSession); + + // only render if it has children! + if (this.children.length) { + renderSession.spriteBatch.start(); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderWebGL(renderSession); + } + + renderSession.spriteBatch.stop(); + } + + if (this._filters) { + renderSession.filterManager.popFilter(); + } + + if (this._mask) { + renderSession.maskManager.popMask(this.mask, renderSession); + } + + renderSession.drawCount++; + + renderSession.spriteBatch.start(); + } +}; + +/** + * Renders the object using the Canvas renderer + * + * @method _renderCanvas + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderCanvas = function (renderSession) { + // 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; + + core.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession); + + return; + } + else { + var context = renderSession.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderSession.currentBlendMode) { + renderSession.currentBlendMode = this.blendMode; + context.globalCompositeOperation = blendModesCanvas[renderSession.currentBlendMode]; + } + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + var resolution = renderSession.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + core.CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderCanvas(renderSession); + } + + if (this._mask) { + renderSession.maskManager.popMask(renderSession); + } + } +}; + +/** + * 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 core.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 === core.CONST.SHAPES.RECT || type === core.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 === core.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 === core.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 core.CanvasBuffer(bounds.width, bounds.height); + var texture = core.Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new core.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.. + core.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 === core.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 new file mode 100644 index 0000000..9377ef7 --- /dev/null +++ b/src/core/primitives/GraphicsData.js @@ -0,0 +1,23 @@ +/** + * 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; diff --git a/src/core/primitives/index.js b/src/core/primitives/index.js new file mode 100644 index 0000000..7f02649 --- /dev/null +++ b/src/core/primitives/index.js @@ -0,0 +1,14 @@ +/** + * @file Main export of the PIXI primitives library + * @author Mat Groves + * @copyright 2013-2015 GoodBoyDigital + * @license {@link https://github.com/GoodBoyDigital/pixi.js/blob/master/LICENSE|MIT License} + */ + +/** + * @namespace PIXI + */ +module.exports = { + Graphics: require('./Graphics'), + GraphicsData: require('./GraphicsData') +}; diff --git a/src/core/renderers/canvas/CanvasGraphics.js b/src/core/renderers/canvas/CanvasGraphics.js index 2b1797a..ef7e765 100644 --- a/src/core/renderers/canvas/CanvasGraphics.js +++ b/src/core/renderers/canvas/CanvasGraphics.js @@ -1,4 +1,4 @@ -var Graphics = require('../../../primitives/Graphics'); +var CONST = require('../../const'); /** * A set of functions used by the canvas renderer to draw the primitive graphics data. @@ -30,7 +30,7 @@ context.lineWidth = data.lineWidth; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -61,7 +61,7 @@ context.stroke(); } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { if (data.fillColor || data.fillColor === 0) { context.globalAlpha = data.fillAlpha * worldAlpha; @@ -75,7 +75,7 @@ context.strokeRect(shape.x, shape.y, shape.width, shape.height); } } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); @@ -92,7 +92,7 @@ context.stroke(); } } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas var w = shape.width * 2; @@ -130,7 +130,7 @@ context.stroke(); } } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var rx = shape.x; var ry = shape.y; var width = shape.width; @@ -190,7 +190,7 @@ var data = graphics.graphicsData[i]; var shape = data.shape; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -207,18 +207,18 @@ } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { context.beginPath(); context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); context.closePath(); } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas @@ -245,7 +245,7 @@ context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); context.closePath(); } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var pts = shape.points; var rx = pts[0]; diff --git a/src/core/renderers/webgl/managers/WebGLBlendModeManager.js b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js new file mode 100644 index 0000000..9f2f89a --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js @@ -0,0 +1,37 @@ +var WebGLManager = require('./WebGLManager'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLBlendModeManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; +} + +WebGLBlendModeManager.prototype = Object.create(WebGLManager.prototype); +WebGLBlendModeManager.prototype.constructor = WebGLBlendModeManager; +module.exports = WebGLBlendModeManager; + +/** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as BlendModes.ADD + */ +WebGLBlendModeManager.prototype.setBlendMode = function (blendMode) { + if (this.currentBlendMode === blendMode) { + return false; + } + + this.currentBlendMode = blendMode; + + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; +}; diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js new file mode 100644 index 0000000..176e7cd --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -0,0 +1,446 @@ +var WebGLManager = require('./WebGLManager'), + FilterTexture = require('./FilterTexture'), + PixiShader = require('../shaders/PixiShader'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLFilterManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {any[]} + */ + this.filterStack = []; + + /** + * @member {any[]]} + */ + this.texturePool = []; + + /** + * @member {number} + */ + this.offsetX = 0; + + /** + * @member {number} + */ + this.offsetY = 0; + + // listen for context and update necessary buffers + var self = this; + this.renderer.on('context', function () { + self.texturePool.length = 0; + self.initShaderBuffers(); + }); +} + +WebGLFilterManager.prototype = Object.create(WebGLManager.prototype); +WebGLFilterManager.prototype.constructor = WebGLFilterManager; +module.exports = WebGLFilterManager; + +/** + * @param renderer {WebGLRenderer} + * @param buffer {ArrayBuffer} + */ +WebGLFilterManager.prototype.begin = function (renderer, buffer) { + this.renderer = renderer; + this.defaultShader = renderer.shaderManager.defaultShader; + + this.width = renderer.projection.x * 2; + this.height = -renderer.projection.y * 2; + + this.buffer = buffer; +}; + +/** + * Applies the filter and adds it to the current filter stack. + * + * @param filterBlock {object} the filter that will be pushed to the current filter stack + */ +WebGLFilterManager.prototype.pushFilter = function (filterBlock) { + var gl = this.renderer.gl; + + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds(); + + // filter program + // OPTIMISATION - the first filter is free if its a simple color change? + this.filterStack.push(filterBlock); + + var filter = filterBlock.filterPasses[0]; + + this.offsetX += filterBlock._filterArea.x; + this.offsetY += filterBlock._filterArea.y; + + var texture = this.texturePool.pop(); + if (!texture) { + texture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + else { + texture.resize(this.width, this.height); + } + + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea; + + var padding = filter.padding; + filterArea.x -= padding; + filterArea.y -= padding; + filterArea.width += padding * 2; + filterArea.height += padding * 2; + + // cap filter to screen size.. + if (filterArea.x < 0) { + filterArea.x = 0; + } + + if (filterArea.width > this.width) { + filterArea.width = this.width; + } + + if (filterArea.y < 0) { + filterArea.y = 0; + } + + if (filterArea.height > this.height) { + filterArea.height = this.height; + } + + //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); + + // set view port + gl.viewport(0, 0, filterArea.width, filterArea.height); + + projection.x = filterArea.width/2; + projection.y = -filterArea.height/2; + + offset.x = -filterArea.x; + offset.y = -filterArea.y; + + // update projection + // now restore the regular shader.. + // this.renderer.shaderManager.setShader(this.defaultShader); + //gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); + //gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y); + + gl.colorMask(true, true, true, true); + gl.clearColor(0,0,0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + + filterBlock._glFilterTexture = texture; + +}; + +/** + * Removes the last filter from the filter stack and doesn't return it. + * + */ +WebGLFilterManager.prototype.popFilter = function () { + var gl = this.renderer.gl; + + var filterBlock = this.filterStack.pop(); + var filterArea = filterBlock._filterArea; + var texture = filterBlock._glFilterTexture; + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + if (filterBlock.filterPasses.length > 1) { + gl.viewport(0, 0, filterArea.width, filterArea.height); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = 0; + this.vertexArray[1] = filterArea.height; + + this.vertexArray[2] = filterArea.width; + this.vertexArray[3] = filterArea.height; + + this.vertexArray[4] = 0; + this.vertexArray[5] = 0; + + this.vertexArray[6] = filterArea.width; + this.vertexArray[7] = 0; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + // now set the uvs.. + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + var inputTexture = texture; + var outputTexture = this.texturePool.pop(); + if (!outputTexture) { + outputTexture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + outputTexture.resize(this.width, this.height); + + // need to clear this FBO as it may have some left over elements from a previous filter. + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + gl.clear(gl.COLOR_BUFFER_BIT); + + gl.disable(gl.BLEND); + + for (var i = 0; i < filterBlock.filterPasses.length-1; i++) { + var filterPass = filterBlock.filterPasses[i]; + + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); + + // draw texture.. + //filterPass.applyFilterPass(filterArea.width, filterArea.height); + this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height); + + // swap the textures.. + var temp = inputTexture; + inputTexture = outputTexture; + outputTexture = temp; + } + + gl.enable(gl.BLEND); + + texture = inputTexture; + this.texturePool.push(outputTexture); + } + + var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1]; + + this.offsetX -= filterArea.x; + this.offsetY -= filterArea.y; + + var sizeX = this.width; + var sizeY = this.height; + + var offsetX = 0; + var offsetY = 0; + + var buffer = this.buffer; + + // time to render the filters texture to the previous scene + if (this.filterStack.length === 0) { + gl.colorMask(true, true, true, true);//this.transparent); + } + else { + var currentFilter = this.filterStack[this.filterStack.length-1]; + filterArea = currentFilter._filterArea; + + sizeX = filterArea.width; + sizeY = filterArea.height; + + offsetX = filterArea.x; + offsetY = filterArea.y; + + buffer = currentFilter._glFilterTexture.frameBuffer; + } + + // TODO need to remove these global elements.. + projection.x = sizeX/2; + projection.y = -sizeY/2; + + offset.x = offsetX; + offset.y = offsetY; + + filterArea = filterBlock._filterArea; + + var x = filterArea.x-offsetX; + var y = filterArea.y-offsetY; + + // update the buffers.. + // make sure to flip the y! + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = x; + this.vertexArray[1] = y + filterArea.height; + + this.vertexArray[2] = x + filterArea.width; + this.vertexArray[3] = y + filterArea.height; + + this.vertexArray[4] = x; + this.vertexArray[5] = y; + + this.vertexArray[6] = x + filterArea.width; + this.vertexArray[7] = y; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + gl.viewport(0, 0, sizeX, sizeY); + + // bind the buffer + gl.bindFramebuffer(gl.FRAMEBUFFER, buffer ); + + // set the blend mode! + //gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + // apply! + this.applyFilterPass(filter, filterArea, sizeX, sizeY); + + // now restore the regular shader.. should happen automatically now.. + // this.renderer.shaderManager.setShader(this.defaultShader); + // gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2); + // gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY); + + // return the texture to the pool + this.texturePool.push(texture); + filterBlock._glFilterTexture = null; +}; + + +/** + * Applies the filter to the specified area. + * + * @param filter {AbstractFilter} the filter that needs to be applied + * @param filterArea {Texture} TODO - might need an update + * @param width {number} the horizontal range of the filter + * @param height {number} the vertical range of the filter + */ +WebGLFilterManager.prototype.applyFilterPass = function (filter, filterArea, width, height) { + // use program + var gl = this.renderer.gl; + + var shader = filter.shaders[gl.id]; + + if (!shader) { + shader = new PixiShader(gl); + + shader.fragmentSrc = filter.fragmentSrc; + shader.uniforms = filter.uniforms; + shader.init(); + + filter.shaders[gl.id] = shader; + } + + // set the shader + this.renderer.shaderManager.setShader(shader); + +// gl.useProgram(shader.program); + + gl.uniform2f(shader.projectionVector, width/2, -height/2); + gl.uniform2f(shader.offsetVector, 0,0); + + if (filter.uniforms.dimensions) { + filter.uniforms.dimensions.value[0] = this.width;//width; + filter.uniforms.dimensions.value[1] = this.height;//height; + filter.uniforms.dimensions.value[2] = this.vertexArray[0]; + filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; + } + + shader.syncUniforms(); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + + // draw the filter... + gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); + + this.renderer.drawCount++; +}; + +/** + * Initialises the shader buffers. + * + */ +WebGLFilterManager.prototype.initShaderBuffers = function () { + var gl = this.renderer.gl; + + // create some buffers + this.vertexBuffer = gl.createBuffer(); + this.uvBuffer = gl.createBuffer(); + this.colorBuffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + + // bind and upload the vertexs.. + // keep a reference to the vertexFloatData.. + this.vertexArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW); + + // bind and upload the uv buffer + this.uvArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.uvArray, gl.STATIC_DRAW); + + this.colorArray = new Float32Array([1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.colorArray, gl.STATIC_DRAW); + + // bind and upload the index + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW); + +}; + +/** + * Destroys the filter and removes it from the filter stack. + * + */ +WebGLFilterManager.prototype.destroy = function () { + var gl = this.renderer.gl; + + this.filterStack = null; + + this.offsetX = 0; + this.offsetY = 0; + + // destroy textures + for (var i = 0; i < this.texturePool.length; i++) { + this.texturePool[i].destroy(); + } + + this.texturePool = null; + + //destroy buffers.. + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.uvBuffer); + gl.deleteBuffer(this.colorBuffer); + gl.deleteBuffer(this.indexBuffer); + + this.renderer = null; +}; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js new file mode 100644 index 0000000..9d47b5c --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -0,0 +1,20 @@ +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLManager(renderer) { + /** + * The renderer this manager works for. + * + * @member {WebGLRenderer} + */ + this.renderer = renderer; +} + +WebGLManager.prototype.constructor = WebGLManager; +module.exports = WebGLManager; + +WebGLManager.prototype.destroy = function () { + this.renderer = null; +}; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js new file mode 100644 index 0000000..6d46d85 --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -0,0 +1,41 @@ +var WebGLManager = require('./WebGLManager'), + WebGLGraphics = require('./WebGLGraphics'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLMaskManager(renderer) { + WebGLManager.call(this, renderer); +} + +WebGLMaskManager.prototype = Object.create(WebGLManager.prototype); +WebGLMaskManager.prototype.constructor = WebGLMaskManager; +module.exports = WebGLMaskManager; + +/** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {any[]} + */ +WebGLMaskManager.prototype.pushMask = function (maskData) { + if (maskData.dirty) { + WebGLGraphics.updateGraphics(maskData, this.renderer.gl); + } + + if (!maskData._webGL[this.renderer.gl.id].data.length) { + return; + } + + this.renderer.stencilManager.pushStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); +}; + +/** + * Removes the last filter from the filter stack and doesn't return it. + * + * @param maskData {any[]} + */ +WebGLMaskManager.prototype.popMask = function (maskData) { + this.renderer.stencilManager.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 new file mode 100644 index 0000000..d02d95d --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -0,0 +1,172 @@ +var WebGLManager = require('./WebGLManager'), + PrimitiveShader = require('../shaders/PrimitiveShader'), + ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), + PixiShader = require('../shaders/PixiShader'), + PixiFastShader = require('../shaders/PixiFastShader'), + StripShader = require('../shaders/StripShader'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLShaderManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {number} + */ + this.maxAttibs = 10; + + /** + * @member {any[]} + */ + this.attribState = []; + + /** + * @member {any[]} + */ + this.tempAttribState = []; + + for (var i = 0; i < this.maxAttibs; i++) { + this.attribState[i] = false; + } + + /** + * @member {any[]} + */ + this.stack = []; + + /** + * @member {number} + * @private + */ + this._currentId = -1; + + /** + * @member {Shader} + * @private + */ + 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; + + // listen for context and update necessary shaders + var self = this; + this.renderer.on('context', function (gl) { + // 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 PixiShader(gl); + + // this shader is used for the fast sprite rendering + self.fastShader = new PixiFastShader(gl); + + // the next one is used for rendering triangle strips + self.stripShader = new StripShader(gl); + + self.setShader(self.defaultShader); + }); +} + +WebGLShaderManager.prototype = Object.create(WebGLManager.prototype); +WebGLShaderManager.prototype.constructor = WebGLShaderManager; +module.exports = WebGLShaderManager; + +/** + * Takes the attributes given in parameters. + * + * @param attribs {Array} attribs + */ +WebGLShaderManager.prototype.setAttribs = function (attribs) { + // reset temp state + var i; + + for (i = 0; i < this.tempAttribState.length; i++) { + this.tempAttribState[i] = false; + } + + // set the new attribs + for (var a in attribs) { + this.tempAttribState[attribs[a]] = true; + } + + var gl = this.renderer.gl; + + for (i = 0; i < this.attribState.length; i++) { + if (this.attribState[i] !== this.tempAttribState[i]) { + this.attribState[i] = this.tempAttribState[i]; + + if (this.attribState[i]) { + gl.enableVertexAttribArray(i); + } + else { + gl.disableVertexAttribArray(i); + } + } + } +}; + +/** + * Sets the current shader. + * + * @param shader {Any} + */ +WebGLShaderManager.prototype.setShader = function (shader) { + if (this._currentId === shader.uuid) { + return false; + } + + this._currentId = shader.uuid; + + this.currentShader = shader; + + this.renderer.gl.useProgram(shader.program); + this.setAttribs(shader.attributes); + + return true; +}; + +/** + * Destroys this object. + * + */ +WebGLShaderManager.prototype.destroy = function () { + 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/managers/WebGLStencilManager.js b/src/core/renderers/webgl/managers/WebGLStencilManager.js new file mode 100644 index 0000000..0bedae6 --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLStencilManager.js @@ -0,0 +1,256 @@ +var WebGLManager = require('./WebGLManager'), + utils = require('../../../utils'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLStencilManager(renderer) { + WebGLManager.call(this, renderer); + + this.stencilStack = []; + this.reverse = true; + this.count = 0; +} + +WebGLStencilManager.prototype = Object.create(WebGLManager.prototype); +WebGLStencilManager.prototype.constructor = WebGLStencilManager; +module.exports = WebGLStencilManager; + +/** + * Applies the Mask and adds it to the current filter stack. + * + * @param graphics {Graphics} + * @param webGLData {any[]} + */ +WebGLStencilManager.prototype.pushStencil = function (graphics, webGLData) { + var gl = this.renderer.gl; + + this.bindGraphics(graphics, webGLData, this.renderer); + + if (this.stencilStack.length === 0) { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + this.reverse = true; + this.count = 0; + } + + this.stencilStack.push(webGLData); + + var level = this.count; + + gl.colorMask(false, false, false, false); + + gl.stencilFunc(gl.ALWAYS,0,0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); + + // draw the triangle strip! + + if (webGLData.mode === 1) { + gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + + // draw a quad to increment.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + } + + this.reverse = !this.reverse; + } + else { + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + } + } + + gl.colorMask(true, true, true, true); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + + this.count++; +}; + +/** + * TODO this does not belong here! + * + * @param graphics {Graphics} + * @param webGLData {Array} + */ +WebGLStencilManager.prototype.bindGraphics = function (graphics, webGLData) { + //if (this._currentGraphics === graphics)return; + this._currentGraphics = graphics; + + var gl = this.renderer.gl; + + // bind the graphics object.. + var projection = this.renderer.projection, + offset = this.renderer.offset, + shader;// = this.renderer.shaderManager.primitiveShader; + + if (webGLData.mode === 1) { + shader = this.renderer.shaderManager.complexPrimitiveShader; + + this.renderer.shaderManager.setShader(shader); + + gl.uniform1f(shader.flipY, this.renderer.flipY); + + gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); + + gl.uniform2f(shader.projectionVector, projection.x, -projection.y); + gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); + + gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); + gl.uniform3fv(shader.color, webGLData.color); + + gl.uniform1f(shader.alpha, graphics.worldAlpha * webGLData.alpha); + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 2, 0); + + + // now do the rest.. + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + } + else { + //this.renderer.shaderManager.activatePrimitiveShader(); + shader = this.renderer.shaderManager.primitiveShader; + this.renderer.shaderManager.setShader( shader ); + + gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); + + gl.uniform1f(shader.flipY, this.renderer.flipY); + gl.uniform2f(shader.projectionVector, projection.x, -projection.y); + gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); + + gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); + + gl.uniform1f(shader.alpha, graphics.worldAlpha); + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); + gl.vertexAttribPointer(shader.colorAttribute, 4, gl.FLOAT, false,4 * 6, 2 * 4); + + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + } +}; + +/** + * @param graphics {Graphics} + * @param webGLData {Array} + */ +WebGLStencilManager.prototype.popStencil = function (graphics, webGLData) { + var gl = this.renderer.gl; + + this.stencilStack.pop(); + + this.count--; + + if (this.stencilStack.length === 0) { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + + } + else { + + var level = this.count; + + this.bindGraphics(graphics, webGLData, this.renderer); + + gl.colorMask(false, false, false, false); + + if (webGLData.mode === 1) { + this.reverse = !this.reverse; + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + + // draw a quad to increment.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + gl.stencilFunc(gl.ALWAYS,0,0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); + + // draw the triangle strip! + gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + } + + } + else { + // console.log("<<>>") + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + } + } + + gl.colorMask(true, true, true, true); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + + + } +}; + +/** + * Destroys the mask stack. + * + */ +WebGLStencilManager.prototype.destroy = function () { + this.renderer = null; + this.stencilStack = null; +}; diff --git a/src/core/renderers/webgl/utils/WebGLBlendModeManager.js b/src/core/renderers/webgl/utils/WebGLBlendModeManager.js deleted file mode 100644 index 9f2f89a..0000000 --- a/src/core/renderers/webgl/utils/WebGLBlendModeManager.js +++ /dev/null @@ -1,37 +0,0 @@ -var WebGLManager = require('./WebGLManager'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLBlendModeManager(renderer) { - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -WebGLBlendModeManager.prototype = Object.create(WebGLManager.prototype); -WebGLBlendModeManager.prototype.constructor = WebGLBlendModeManager; -module.exports = WebGLBlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as BlendModes.ADD - */ -WebGLBlendModeManager.prototype.setBlendMode = function (blendMode) { - if (this.currentBlendMode === blendMode) { - return false; - } - - this.currentBlendMode = blendMode; - - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); - - return true; -}; diff --git a/src/core/const.js b/src/core/const.js index 2803e5b..aea72ec 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -130,5 +130,25 @@ backgroundColor: 0x000000, clearBeforeRender: true, preserveDrawingBuffer: false + }, + + /** + * Constants that identify shapes, mainly to prevent `instanceof` calls. + * + * @static + * @constant + * @property {object} SHAPES + * @property {object} SHAPES.POLY=0 + * @property {object} SHAPES.RECT=1 + * @property {object} SHAPES.CIRC=2 + * @property {object} SHAPES.ELIP=3 + * @property {object} SHAPES.RREC=4 + */ + SHAPES: { + POLY = 0; + RECT = 1; + CIRC = 2; + ELIP = 3; + RREC = 4; } }; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index bfd59cf..7c0e7c6 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -95,7 +95,7 @@ this.filterArea = null; /** - * cached sin rotation and cos rotation + * cached sin rotation * * @member {number} * @private @@ -103,7 +103,7 @@ this._sr = 0; /** - * cached sin rotation and cos rotation + * cached cos rotation * * @member {number} * @private @@ -400,7 +400,7 @@ DisplayObject.prototype.generateTexture = function (resolution, scaleMode, renderer) { var bounds = this.getLocalBounds(); - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); _tempMatrix.tx = -bounds.x; _tempMatrix.ty = -bounds.y; @@ -474,7 +474,7 @@ var bounds = this.getLocalBounds(); if (!this._cachedSprite) { - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0); //, renderer); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0); this._cachedSprite = new Sprite(renderTexture); this._cachedSprite.worldTransform = this.worldTransform; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 3f6a3b6..69cfdc9 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Circle object can be used to specify a hit area for displayObjects @@ -29,10 +30,11 @@ this.radius = radius || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.CIRC in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.CIRC; } Circle.prototype.constructor = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index fc9df4d..4f2d066 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Ellipse object can be used to specify a hit area for displayObjects @@ -36,10 +37,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.ELIP in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.ELIP; } Ellipse.prototype.constructor = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index 5d4a464..ae89148 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -1,4 +1,5 @@ -var Point = require('../Point'); +var Point = require('../Point'), + CONST = require('../../const'); /** * @class @@ -33,6 +34,13 @@ * @member {Point[]} */ this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + */ + this.type = CONST.SHAPES.POLY; } Polygon.prototype.constructor = Polygon; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index c3454b5..16ac62a 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * the Rectangle object is an area defined by its position, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -34,10 +36,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RECT; } Rectangle.prototype.constructor = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0f2abfc..27d286e 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * The Rounded Rectangle object is an area defined by its position and has nice rounded corners, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -41,10 +43,11 @@ this.radius = radius || 20; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RRECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RREC; } RoundedRectangle.prototype.constructor = RoundedRectangle; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js new file mode 100644 index 0000000..42647c5 --- /dev/null +++ b/src/core/primitives/Graphics.js @@ -0,0 +1,1029 @@ +var core = require('../core'), + GraphicsData = require('./GraphicsData'); + +/** + * 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() { + core.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 {object[]} + * @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 = core.CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {object} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object[]} + * @private + */ + 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 core.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(core.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; + } + } + } +}); + +/** + * 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 core.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 core.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; + + 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 core.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 core.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 core.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 core.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 core.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 core.CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = core.Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + core.CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @method _renderWebGL + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderWebGL = function (renderSession) { + // 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 on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + core.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); + + return; + } + else { + renderSession.spriteBatch.stop(); + renderSession.blendModeManager.setBlendMode(this.blendMode); + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + if (this._filters) { + renderSession.filterManager.pushFilter(this._filterBlock); + } + + // check blend mode + if (this.blendMode !== renderSession.spriteBatch.currentBlendMode) { + renderSession.spriteBatch.currentBlendMode = this.blendMode; + + var blendModeWebGL = blendModesWebGL[renderSession.spriteBatch.currentBlendMode]; + + renderSession.spriteBatch.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); + } + + // check if the webgl graphic needs to be updated + if (this.glDirty) { + this.dirty = true; + this.glDirty = false; + } + + core.WebGLGraphics.renderGraphics(this, renderSession); + + // only render if it has children! + if (this.children.length) { + renderSession.spriteBatch.start(); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderWebGL(renderSession); + } + + renderSession.spriteBatch.stop(); + } + + if (this._filters) { + renderSession.filterManager.popFilter(); + } + + if (this._mask) { + renderSession.maskManager.popMask(this.mask, renderSession); + } + + renderSession.drawCount++; + + renderSession.spriteBatch.start(); + } +}; + +/** + * Renders the object using the Canvas renderer + * + * @method _renderCanvas + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderCanvas = function (renderSession) { + // 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; + + core.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession); + + return; + } + else { + var context = renderSession.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderSession.currentBlendMode) { + renderSession.currentBlendMode = this.blendMode; + context.globalCompositeOperation = blendModesCanvas[renderSession.currentBlendMode]; + } + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + var resolution = renderSession.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + core.CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderCanvas(renderSession); + } + + if (this._mask) { + renderSession.maskManager.popMask(renderSession); + } + } +}; + +/** + * 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 core.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 === core.CONST.SHAPES.RECT || type === core.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 === core.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 === core.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 core.CanvasBuffer(bounds.width, bounds.height); + var texture = core.Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new core.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.. + core.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 === core.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 new file mode 100644 index 0000000..9377ef7 --- /dev/null +++ b/src/core/primitives/GraphicsData.js @@ -0,0 +1,23 @@ +/** + * 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; diff --git a/src/core/primitives/index.js b/src/core/primitives/index.js new file mode 100644 index 0000000..7f02649 --- /dev/null +++ b/src/core/primitives/index.js @@ -0,0 +1,14 @@ +/** + * @file Main export of the PIXI primitives library + * @author Mat Groves + * @copyright 2013-2015 GoodBoyDigital + * @license {@link https://github.com/GoodBoyDigital/pixi.js/blob/master/LICENSE|MIT License} + */ + +/** + * @namespace PIXI + */ +module.exports = { + Graphics: require('./Graphics'), + GraphicsData: require('./GraphicsData') +}; diff --git a/src/core/renderers/canvas/CanvasGraphics.js b/src/core/renderers/canvas/CanvasGraphics.js index 2b1797a..ef7e765 100644 --- a/src/core/renderers/canvas/CanvasGraphics.js +++ b/src/core/renderers/canvas/CanvasGraphics.js @@ -1,4 +1,4 @@ -var Graphics = require('../../../primitives/Graphics'); +var CONST = require('../../const'); /** * A set of functions used by the canvas renderer to draw the primitive graphics data. @@ -30,7 +30,7 @@ context.lineWidth = data.lineWidth; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -61,7 +61,7 @@ context.stroke(); } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { if (data.fillColor || data.fillColor === 0) { context.globalAlpha = data.fillAlpha * worldAlpha; @@ -75,7 +75,7 @@ context.strokeRect(shape.x, shape.y, shape.width, shape.height); } } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); @@ -92,7 +92,7 @@ context.stroke(); } } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas var w = shape.width * 2; @@ -130,7 +130,7 @@ context.stroke(); } } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var rx = shape.x; var ry = shape.y; var width = shape.width; @@ -190,7 +190,7 @@ var data = graphics.graphicsData[i]; var shape = data.shape; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -207,18 +207,18 @@ } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { context.beginPath(); context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); context.closePath(); } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas @@ -245,7 +245,7 @@ context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); context.closePath(); } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var pts = shape.points; var rx = pts[0]; diff --git a/src/core/renderers/webgl/managers/WebGLBlendModeManager.js b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js new file mode 100644 index 0000000..9f2f89a --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js @@ -0,0 +1,37 @@ +var WebGLManager = require('./WebGLManager'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLBlendModeManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; +} + +WebGLBlendModeManager.prototype = Object.create(WebGLManager.prototype); +WebGLBlendModeManager.prototype.constructor = WebGLBlendModeManager; +module.exports = WebGLBlendModeManager; + +/** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as BlendModes.ADD + */ +WebGLBlendModeManager.prototype.setBlendMode = function (blendMode) { + if (this.currentBlendMode === blendMode) { + return false; + } + + this.currentBlendMode = blendMode; + + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; +}; diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js new file mode 100644 index 0000000..176e7cd --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -0,0 +1,446 @@ +var WebGLManager = require('./WebGLManager'), + FilterTexture = require('./FilterTexture'), + PixiShader = require('../shaders/PixiShader'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLFilterManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {any[]} + */ + this.filterStack = []; + + /** + * @member {any[]]} + */ + this.texturePool = []; + + /** + * @member {number} + */ + this.offsetX = 0; + + /** + * @member {number} + */ + this.offsetY = 0; + + // listen for context and update necessary buffers + var self = this; + this.renderer.on('context', function () { + self.texturePool.length = 0; + self.initShaderBuffers(); + }); +} + +WebGLFilterManager.prototype = Object.create(WebGLManager.prototype); +WebGLFilterManager.prototype.constructor = WebGLFilterManager; +module.exports = WebGLFilterManager; + +/** + * @param renderer {WebGLRenderer} + * @param buffer {ArrayBuffer} + */ +WebGLFilterManager.prototype.begin = function (renderer, buffer) { + this.renderer = renderer; + this.defaultShader = renderer.shaderManager.defaultShader; + + this.width = renderer.projection.x * 2; + this.height = -renderer.projection.y * 2; + + this.buffer = buffer; +}; + +/** + * Applies the filter and adds it to the current filter stack. + * + * @param filterBlock {object} the filter that will be pushed to the current filter stack + */ +WebGLFilterManager.prototype.pushFilter = function (filterBlock) { + var gl = this.renderer.gl; + + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds(); + + // filter program + // OPTIMISATION - the first filter is free if its a simple color change? + this.filterStack.push(filterBlock); + + var filter = filterBlock.filterPasses[0]; + + this.offsetX += filterBlock._filterArea.x; + this.offsetY += filterBlock._filterArea.y; + + var texture = this.texturePool.pop(); + if (!texture) { + texture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + else { + texture.resize(this.width, this.height); + } + + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea; + + var padding = filter.padding; + filterArea.x -= padding; + filterArea.y -= padding; + filterArea.width += padding * 2; + filterArea.height += padding * 2; + + // cap filter to screen size.. + if (filterArea.x < 0) { + filterArea.x = 0; + } + + if (filterArea.width > this.width) { + filterArea.width = this.width; + } + + if (filterArea.y < 0) { + filterArea.y = 0; + } + + if (filterArea.height > this.height) { + filterArea.height = this.height; + } + + //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); + + // set view port + gl.viewport(0, 0, filterArea.width, filterArea.height); + + projection.x = filterArea.width/2; + projection.y = -filterArea.height/2; + + offset.x = -filterArea.x; + offset.y = -filterArea.y; + + // update projection + // now restore the regular shader.. + // this.renderer.shaderManager.setShader(this.defaultShader); + //gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); + //gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y); + + gl.colorMask(true, true, true, true); + gl.clearColor(0,0,0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + + filterBlock._glFilterTexture = texture; + +}; + +/** + * Removes the last filter from the filter stack and doesn't return it. + * + */ +WebGLFilterManager.prototype.popFilter = function () { + var gl = this.renderer.gl; + + var filterBlock = this.filterStack.pop(); + var filterArea = filterBlock._filterArea; + var texture = filterBlock._glFilterTexture; + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + if (filterBlock.filterPasses.length > 1) { + gl.viewport(0, 0, filterArea.width, filterArea.height); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = 0; + this.vertexArray[1] = filterArea.height; + + this.vertexArray[2] = filterArea.width; + this.vertexArray[3] = filterArea.height; + + this.vertexArray[4] = 0; + this.vertexArray[5] = 0; + + this.vertexArray[6] = filterArea.width; + this.vertexArray[7] = 0; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + // now set the uvs.. + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + var inputTexture = texture; + var outputTexture = this.texturePool.pop(); + if (!outputTexture) { + outputTexture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + outputTexture.resize(this.width, this.height); + + // need to clear this FBO as it may have some left over elements from a previous filter. + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + gl.clear(gl.COLOR_BUFFER_BIT); + + gl.disable(gl.BLEND); + + for (var i = 0; i < filterBlock.filterPasses.length-1; i++) { + var filterPass = filterBlock.filterPasses[i]; + + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); + + // draw texture.. + //filterPass.applyFilterPass(filterArea.width, filterArea.height); + this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height); + + // swap the textures.. + var temp = inputTexture; + inputTexture = outputTexture; + outputTexture = temp; + } + + gl.enable(gl.BLEND); + + texture = inputTexture; + this.texturePool.push(outputTexture); + } + + var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1]; + + this.offsetX -= filterArea.x; + this.offsetY -= filterArea.y; + + var sizeX = this.width; + var sizeY = this.height; + + var offsetX = 0; + var offsetY = 0; + + var buffer = this.buffer; + + // time to render the filters texture to the previous scene + if (this.filterStack.length === 0) { + gl.colorMask(true, true, true, true);//this.transparent); + } + else { + var currentFilter = this.filterStack[this.filterStack.length-1]; + filterArea = currentFilter._filterArea; + + sizeX = filterArea.width; + sizeY = filterArea.height; + + offsetX = filterArea.x; + offsetY = filterArea.y; + + buffer = currentFilter._glFilterTexture.frameBuffer; + } + + // TODO need to remove these global elements.. + projection.x = sizeX/2; + projection.y = -sizeY/2; + + offset.x = offsetX; + offset.y = offsetY; + + filterArea = filterBlock._filterArea; + + var x = filterArea.x-offsetX; + var y = filterArea.y-offsetY; + + // update the buffers.. + // make sure to flip the y! + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = x; + this.vertexArray[1] = y + filterArea.height; + + this.vertexArray[2] = x + filterArea.width; + this.vertexArray[3] = y + filterArea.height; + + this.vertexArray[4] = x; + this.vertexArray[5] = y; + + this.vertexArray[6] = x + filterArea.width; + this.vertexArray[7] = y; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + gl.viewport(0, 0, sizeX, sizeY); + + // bind the buffer + gl.bindFramebuffer(gl.FRAMEBUFFER, buffer ); + + // set the blend mode! + //gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + // apply! + this.applyFilterPass(filter, filterArea, sizeX, sizeY); + + // now restore the regular shader.. should happen automatically now.. + // this.renderer.shaderManager.setShader(this.defaultShader); + // gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2); + // gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY); + + // return the texture to the pool + this.texturePool.push(texture); + filterBlock._glFilterTexture = null; +}; + + +/** + * Applies the filter to the specified area. + * + * @param filter {AbstractFilter} the filter that needs to be applied + * @param filterArea {Texture} TODO - might need an update + * @param width {number} the horizontal range of the filter + * @param height {number} the vertical range of the filter + */ +WebGLFilterManager.prototype.applyFilterPass = function (filter, filterArea, width, height) { + // use program + var gl = this.renderer.gl; + + var shader = filter.shaders[gl.id]; + + if (!shader) { + shader = new PixiShader(gl); + + shader.fragmentSrc = filter.fragmentSrc; + shader.uniforms = filter.uniforms; + shader.init(); + + filter.shaders[gl.id] = shader; + } + + // set the shader + this.renderer.shaderManager.setShader(shader); + +// gl.useProgram(shader.program); + + gl.uniform2f(shader.projectionVector, width/2, -height/2); + gl.uniform2f(shader.offsetVector, 0,0); + + if (filter.uniforms.dimensions) { + filter.uniforms.dimensions.value[0] = this.width;//width; + filter.uniforms.dimensions.value[1] = this.height;//height; + filter.uniforms.dimensions.value[2] = this.vertexArray[0]; + filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; + } + + shader.syncUniforms(); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + + // draw the filter... + gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); + + this.renderer.drawCount++; +}; + +/** + * Initialises the shader buffers. + * + */ +WebGLFilterManager.prototype.initShaderBuffers = function () { + var gl = this.renderer.gl; + + // create some buffers + this.vertexBuffer = gl.createBuffer(); + this.uvBuffer = gl.createBuffer(); + this.colorBuffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + + // bind and upload the vertexs.. + // keep a reference to the vertexFloatData.. + this.vertexArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW); + + // bind and upload the uv buffer + this.uvArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.uvArray, gl.STATIC_DRAW); + + this.colorArray = new Float32Array([1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.colorArray, gl.STATIC_DRAW); + + // bind and upload the index + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW); + +}; + +/** + * Destroys the filter and removes it from the filter stack. + * + */ +WebGLFilterManager.prototype.destroy = function () { + var gl = this.renderer.gl; + + this.filterStack = null; + + this.offsetX = 0; + this.offsetY = 0; + + // destroy textures + for (var i = 0; i < this.texturePool.length; i++) { + this.texturePool[i].destroy(); + } + + this.texturePool = null; + + //destroy buffers.. + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.uvBuffer); + gl.deleteBuffer(this.colorBuffer); + gl.deleteBuffer(this.indexBuffer); + + this.renderer = null; +}; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js new file mode 100644 index 0000000..9d47b5c --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -0,0 +1,20 @@ +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLManager(renderer) { + /** + * The renderer this manager works for. + * + * @member {WebGLRenderer} + */ + this.renderer = renderer; +} + +WebGLManager.prototype.constructor = WebGLManager; +module.exports = WebGLManager; + +WebGLManager.prototype.destroy = function () { + this.renderer = null; +}; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js new file mode 100644 index 0000000..6d46d85 --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -0,0 +1,41 @@ +var WebGLManager = require('./WebGLManager'), + WebGLGraphics = require('./WebGLGraphics'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLMaskManager(renderer) { + WebGLManager.call(this, renderer); +} + +WebGLMaskManager.prototype = Object.create(WebGLManager.prototype); +WebGLMaskManager.prototype.constructor = WebGLMaskManager; +module.exports = WebGLMaskManager; + +/** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {any[]} + */ +WebGLMaskManager.prototype.pushMask = function (maskData) { + if (maskData.dirty) { + WebGLGraphics.updateGraphics(maskData, this.renderer.gl); + } + + if (!maskData._webGL[this.renderer.gl.id].data.length) { + return; + } + + this.renderer.stencilManager.pushStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); +}; + +/** + * Removes the last filter from the filter stack and doesn't return it. + * + * @param maskData {any[]} + */ +WebGLMaskManager.prototype.popMask = function (maskData) { + this.renderer.stencilManager.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 new file mode 100644 index 0000000..d02d95d --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -0,0 +1,172 @@ +var WebGLManager = require('./WebGLManager'), + PrimitiveShader = require('../shaders/PrimitiveShader'), + ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), + PixiShader = require('../shaders/PixiShader'), + PixiFastShader = require('../shaders/PixiFastShader'), + StripShader = require('../shaders/StripShader'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLShaderManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {number} + */ + this.maxAttibs = 10; + + /** + * @member {any[]} + */ + this.attribState = []; + + /** + * @member {any[]} + */ + this.tempAttribState = []; + + for (var i = 0; i < this.maxAttibs; i++) { + this.attribState[i] = false; + } + + /** + * @member {any[]} + */ + this.stack = []; + + /** + * @member {number} + * @private + */ + this._currentId = -1; + + /** + * @member {Shader} + * @private + */ + 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; + + // listen for context and update necessary shaders + var self = this; + this.renderer.on('context', function (gl) { + // 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 PixiShader(gl); + + // this shader is used for the fast sprite rendering + self.fastShader = new PixiFastShader(gl); + + // the next one is used for rendering triangle strips + self.stripShader = new StripShader(gl); + + self.setShader(self.defaultShader); + }); +} + +WebGLShaderManager.prototype = Object.create(WebGLManager.prototype); +WebGLShaderManager.prototype.constructor = WebGLShaderManager; +module.exports = WebGLShaderManager; + +/** + * Takes the attributes given in parameters. + * + * @param attribs {Array} attribs + */ +WebGLShaderManager.prototype.setAttribs = function (attribs) { + // reset temp state + var i; + + for (i = 0; i < this.tempAttribState.length; i++) { + this.tempAttribState[i] = false; + } + + // set the new attribs + for (var a in attribs) { + this.tempAttribState[attribs[a]] = true; + } + + var gl = this.renderer.gl; + + for (i = 0; i < this.attribState.length; i++) { + if (this.attribState[i] !== this.tempAttribState[i]) { + this.attribState[i] = this.tempAttribState[i]; + + if (this.attribState[i]) { + gl.enableVertexAttribArray(i); + } + else { + gl.disableVertexAttribArray(i); + } + } + } +}; + +/** + * Sets the current shader. + * + * @param shader {Any} + */ +WebGLShaderManager.prototype.setShader = function (shader) { + if (this._currentId === shader.uuid) { + return false; + } + + this._currentId = shader.uuid; + + this.currentShader = shader; + + this.renderer.gl.useProgram(shader.program); + this.setAttribs(shader.attributes); + + return true; +}; + +/** + * Destroys this object. + * + */ +WebGLShaderManager.prototype.destroy = function () { + 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/managers/WebGLStencilManager.js b/src/core/renderers/webgl/managers/WebGLStencilManager.js new file mode 100644 index 0000000..0bedae6 --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLStencilManager.js @@ -0,0 +1,256 @@ +var WebGLManager = require('./WebGLManager'), + utils = require('../../../utils'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLStencilManager(renderer) { + WebGLManager.call(this, renderer); + + this.stencilStack = []; + this.reverse = true; + this.count = 0; +} + +WebGLStencilManager.prototype = Object.create(WebGLManager.prototype); +WebGLStencilManager.prototype.constructor = WebGLStencilManager; +module.exports = WebGLStencilManager; + +/** + * Applies the Mask and adds it to the current filter stack. + * + * @param graphics {Graphics} + * @param webGLData {any[]} + */ +WebGLStencilManager.prototype.pushStencil = function (graphics, webGLData) { + var gl = this.renderer.gl; + + this.bindGraphics(graphics, webGLData, this.renderer); + + if (this.stencilStack.length === 0) { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + this.reverse = true; + this.count = 0; + } + + this.stencilStack.push(webGLData); + + var level = this.count; + + gl.colorMask(false, false, false, false); + + gl.stencilFunc(gl.ALWAYS,0,0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); + + // draw the triangle strip! + + if (webGLData.mode === 1) { + gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + + // draw a quad to increment.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + } + + this.reverse = !this.reverse; + } + else { + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + } + } + + gl.colorMask(true, true, true, true); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + + this.count++; +}; + +/** + * TODO this does not belong here! + * + * @param graphics {Graphics} + * @param webGLData {Array} + */ +WebGLStencilManager.prototype.bindGraphics = function (graphics, webGLData) { + //if (this._currentGraphics === graphics)return; + this._currentGraphics = graphics; + + var gl = this.renderer.gl; + + // bind the graphics object.. + var projection = this.renderer.projection, + offset = this.renderer.offset, + shader;// = this.renderer.shaderManager.primitiveShader; + + if (webGLData.mode === 1) { + shader = this.renderer.shaderManager.complexPrimitiveShader; + + this.renderer.shaderManager.setShader(shader); + + gl.uniform1f(shader.flipY, this.renderer.flipY); + + gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); + + gl.uniform2f(shader.projectionVector, projection.x, -projection.y); + gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); + + gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); + gl.uniform3fv(shader.color, webGLData.color); + + gl.uniform1f(shader.alpha, graphics.worldAlpha * webGLData.alpha); + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 2, 0); + + + // now do the rest.. + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + } + else { + //this.renderer.shaderManager.activatePrimitiveShader(); + shader = this.renderer.shaderManager.primitiveShader; + this.renderer.shaderManager.setShader( shader ); + + gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); + + gl.uniform1f(shader.flipY, this.renderer.flipY); + gl.uniform2f(shader.projectionVector, projection.x, -projection.y); + gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); + + gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); + + gl.uniform1f(shader.alpha, graphics.worldAlpha); + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); + gl.vertexAttribPointer(shader.colorAttribute, 4, gl.FLOAT, false,4 * 6, 2 * 4); + + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + } +}; + +/** + * @param graphics {Graphics} + * @param webGLData {Array} + */ +WebGLStencilManager.prototype.popStencil = function (graphics, webGLData) { + var gl = this.renderer.gl; + + this.stencilStack.pop(); + + this.count--; + + if (this.stencilStack.length === 0) { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + + } + else { + + var level = this.count; + + this.bindGraphics(graphics, webGLData, this.renderer); + + gl.colorMask(false, false, false, false); + + if (webGLData.mode === 1) { + this.reverse = !this.reverse; + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + + // draw a quad to increment.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + gl.stencilFunc(gl.ALWAYS,0,0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); + + // draw the triangle strip! + gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + } + + } + else { + // console.log("<<>>") + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + } + } + + gl.colorMask(true, true, true, true); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + + + } +}; + +/** + * Destroys the mask stack. + * + */ +WebGLStencilManager.prototype.destroy = function () { + this.renderer = null; + this.stencilStack = null; +}; diff --git a/src/core/renderers/webgl/utils/WebGLBlendModeManager.js b/src/core/renderers/webgl/utils/WebGLBlendModeManager.js deleted file mode 100644 index 9f2f89a..0000000 --- a/src/core/renderers/webgl/utils/WebGLBlendModeManager.js +++ /dev/null @@ -1,37 +0,0 @@ -var WebGLManager = require('./WebGLManager'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLBlendModeManager(renderer) { - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -WebGLBlendModeManager.prototype = Object.create(WebGLManager.prototype); -WebGLBlendModeManager.prototype.constructor = WebGLBlendModeManager; -module.exports = WebGLBlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as BlendModes.ADD - */ -WebGLBlendModeManager.prototype.setBlendMode = function (blendMode) { - if (this.currentBlendMode === blendMode) { - return false; - } - - this.currentBlendMode = blendMode; - - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); - - return true; -}; diff --git a/src/core/renderers/webgl/utils/WebGLFilterManager.js b/src/core/renderers/webgl/utils/WebGLFilterManager.js deleted file mode 100644 index 176e7cd..0000000 --- a/src/core/renderers/webgl/utils/WebGLFilterManager.js +++ /dev/null @@ -1,446 +0,0 @@ -var WebGLManager = require('./WebGLManager'), - FilterTexture = require('./FilterTexture'), - PixiShader = require('../shaders/PixiShader'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLFilterManager(renderer) { - WebGLManager.call(this, renderer); - - /** - * @member {any[]} - */ - this.filterStack = []; - - /** - * @member {any[]]} - */ - this.texturePool = []; - - /** - * @member {number} - */ - this.offsetX = 0; - - /** - * @member {number} - */ - this.offsetY = 0; - - // listen for context and update necessary buffers - var self = this; - this.renderer.on('context', function () { - self.texturePool.length = 0; - self.initShaderBuffers(); - }); -} - -WebGLFilterManager.prototype = Object.create(WebGLManager.prototype); -WebGLFilterManager.prototype.constructor = WebGLFilterManager; -module.exports = WebGLFilterManager; - -/** - * @param renderer {WebGLRenderer} - * @param buffer {ArrayBuffer} - */ -WebGLFilterManager.prototype.begin = function (renderer, buffer) { - this.renderer = renderer; - this.defaultShader = renderer.shaderManager.defaultShader; - - this.width = renderer.projection.x * 2; - this.height = -renderer.projection.y * 2; - - this.buffer = buffer; -}; - -/** - * Applies the filter and adds it to the current filter stack. - * - * @param filterBlock {object} the filter that will be pushed to the current filter stack - */ -WebGLFilterManager.prototype.pushFilter = function (filterBlock) { - var gl = this.renderer.gl; - - var projection = this.renderer.projection; - var offset = this.renderer.offset; - - filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds(); - - // filter program - // OPTIMISATION - the first filter is free if its a simple color change? - this.filterStack.push(filterBlock); - - var filter = filterBlock.filterPasses[0]; - - this.offsetX += filterBlock._filterArea.x; - this.offsetY += filterBlock._filterArea.y; - - var texture = this.texturePool.pop(); - if (!texture) { - texture = new FilterTexture(this.renderer.gl, this.width, this.height); - } - else { - texture.resize(this.width, this.height); - } - - gl.bindTexture(gl.TEXTURE_2D, texture.texture); - - var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea; - - var padding = filter.padding; - filterArea.x -= padding; - filterArea.y -= padding; - filterArea.width += padding * 2; - filterArea.height += padding * 2; - - // cap filter to screen size.. - if (filterArea.x < 0) { - filterArea.x = 0; - } - - if (filterArea.width > this.width) { - filterArea.width = this.width; - } - - if (filterArea.y < 0) { - filterArea.y = 0; - } - - if (filterArea.height > this.height) { - filterArea.height = this.height; - } - - //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); - - // set view port - gl.viewport(0, 0, filterArea.width, filterArea.height); - - projection.x = filterArea.width/2; - projection.y = -filterArea.height/2; - - offset.x = -filterArea.x; - offset.y = -filterArea.y; - - // update projection - // now restore the regular shader.. - // this.renderer.shaderManager.setShader(this.defaultShader); - //gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); - //gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y); - - gl.colorMask(true, true, true, true); - gl.clearColor(0,0,0, 0); - gl.clear(gl.COLOR_BUFFER_BIT); - - filterBlock._glFilterTexture = texture; - -}; - -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -WebGLFilterManager.prototype.popFilter = function () { - var gl = this.renderer.gl; - - var filterBlock = this.filterStack.pop(); - var filterArea = filterBlock._filterArea; - var texture = filterBlock._glFilterTexture; - var projection = this.renderer.projection; - var offset = this.renderer.offset; - - if (filterBlock.filterPasses.length > 1) { - gl.viewport(0, 0, filterArea.width, filterArea.height); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - - this.vertexArray[0] = 0; - this.vertexArray[1] = filterArea.height; - - this.vertexArray[2] = filterArea.width; - this.vertexArray[3] = filterArea.height; - - this.vertexArray[4] = 0; - this.vertexArray[5] = 0; - - this.vertexArray[6] = filterArea.width; - this.vertexArray[7] = 0; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - // now set the uvs.. - this.uvArray[2] = filterArea.width/this.width; - this.uvArray[5] = filterArea.height/this.height; - this.uvArray[6] = filterArea.width/this.width; - this.uvArray[7] = filterArea.height/this.height; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); - - var inputTexture = texture; - var outputTexture = this.texturePool.pop(); - if (!outputTexture) { - outputTexture = new FilterTexture(this.renderer.gl, this.width, this.height); - } - outputTexture.resize(this.width, this.height); - - // need to clear this FBO as it may have some left over elements from a previous filter. - gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); - gl.clear(gl.COLOR_BUFFER_BIT); - - gl.disable(gl.BLEND); - - for (var i = 0; i < filterBlock.filterPasses.length-1; i++) { - var filterPass = filterBlock.filterPasses[i]; - - gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); - - // set texture - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); - - // draw texture.. - //filterPass.applyFilterPass(filterArea.width, filterArea.height); - this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height); - - // swap the textures.. - var temp = inputTexture; - inputTexture = outputTexture; - outputTexture = temp; - } - - gl.enable(gl.BLEND); - - texture = inputTexture; - this.texturePool.push(outputTexture); - } - - var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1]; - - this.offsetX -= filterArea.x; - this.offsetY -= filterArea.y; - - var sizeX = this.width; - var sizeY = this.height; - - var offsetX = 0; - var offsetY = 0; - - var buffer = this.buffer; - - // time to render the filters texture to the previous scene - if (this.filterStack.length === 0) { - gl.colorMask(true, true, true, true);//this.transparent); - } - else { - var currentFilter = this.filterStack[this.filterStack.length-1]; - filterArea = currentFilter._filterArea; - - sizeX = filterArea.width; - sizeY = filterArea.height; - - offsetX = filterArea.x; - offsetY = filterArea.y; - - buffer = currentFilter._glFilterTexture.frameBuffer; - } - - // TODO need to remove these global elements.. - projection.x = sizeX/2; - projection.y = -sizeY/2; - - offset.x = offsetX; - offset.y = offsetY; - - filterArea = filterBlock._filterArea; - - var x = filterArea.x-offsetX; - var y = filterArea.y-offsetY; - - // update the buffers.. - // make sure to flip the y! - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - - this.vertexArray[0] = x; - this.vertexArray[1] = y + filterArea.height; - - this.vertexArray[2] = x + filterArea.width; - this.vertexArray[3] = y + filterArea.height; - - this.vertexArray[4] = x; - this.vertexArray[5] = y; - - this.vertexArray[6] = x + filterArea.width; - this.vertexArray[7] = y; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - - this.uvArray[2] = filterArea.width/this.width; - this.uvArray[5] = filterArea.height/this.height; - this.uvArray[6] = filterArea.width/this.width; - this.uvArray[7] = filterArea.height/this.height; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); - - gl.viewport(0, 0, sizeX, sizeY); - - // bind the buffer - gl.bindFramebuffer(gl.FRAMEBUFFER, buffer ); - - // set the blend mode! - //gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) - - // set texture - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, texture.texture); - - // apply! - this.applyFilterPass(filter, filterArea, sizeX, sizeY); - - // now restore the regular shader.. should happen automatically now.. - // this.renderer.shaderManager.setShader(this.defaultShader); - // gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2); - // gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY); - - // return the texture to the pool - this.texturePool.push(texture); - filterBlock._glFilterTexture = null; -}; - - -/** - * Applies the filter to the specified area. - * - * @param filter {AbstractFilter} the filter that needs to be applied - * @param filterArea {Texture} TODO - might need an update - * @param width {number} the horizontal range of the filter - * @param height {number} the vertical range of the filter - */ -WebGLFilterManager.prototype.applyFilterPass = function (filter, filterArea, width, height) { - // use program - var gl = this.renderer.gl; - - var shader = filter.shaders[gl.id]; - - if (!shader) { - shader = new PixiShader(gl); - - shader.fragmentSrc = filter.fragmentSrc; - shader.uniforms = filter.uniforms; - shader.init(); - - filter.shaders[gl.id] = shader; - } - - // set the shader - this.renderer.shaderManager.setShader(shader); - -// gl.useProgram(shader.program); - - gl.uniform2f(shader.projectionVector, width/2, -height/2); - gl.uniform2f(shader.offsetVector, 0,0); - - if (filter.uniforms.dimensions) { - filter.uniforms.dimensions.value[0] = this.width;//width; - filter.uniforms.dimensions.value[1] = this.height;//height; - filter.uniforms.dimensions.value[2] = this.vertexArray[0]; - filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; - } - - shader.syncUniforms(); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); - gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - - // draw the filter... - gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); - - this.renderer.drawCount++; -}; - -/** - * Initialises the shader buffers. - * - */ -WebGLFilterManager.prototype.initShaderBuffers = function () { - var gl = this.renderer.gl; - - // create some buffers - this.vertexBuffer = gl.createBuffer(); - this.uvBuffer = gl.createBuffer(); - this.colorBuffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - - // bind and upload the vertexs.. - // keep a reference to the vertexFloatData.. - this.vertexArray = new Float32Array([0.0, 0.0, - 1.0, 0.0, - 0.0, 1.0, - 1.0, 1.0]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW); - - // bind and upload the uv buffer - this.uvArray = new Float32Array([0.0, 0.0, - 1.0, 0.0, - 0.0, 1.0, - 1.0, 1.0]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.uvArray, gl.STATIC_DRAW); - - this.colorArray = new Float32Array([1.0, 0xFFFFFF, - 1.0, 0xFFFFFF, - 1.0, 0xFFFFFF, - 1.0, 0xFFFFFF]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.colorArray, gl.STATIC_DRAW); - - // bind and upload the index - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW); - -}; - -/** - * Destroys the filter and removes it from the filter stack. - * - */ -WebGLFilterManager.prototype.destroy = function () { - var gl = this.renderer.gl; - - this.filterStack = null; - - this.offsetX = 0; - this.offsetY = 0; - - // destroy textures - for (var i = 0; i < this.texturePool.length; i++) { - this.texturePool[i].destroy(); - } - - this.texturePool = null; - - //destroy buffers.. - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.uvBuffer); - gl.deleteBuffer(this.colorBuffer); - gl.deleteBuffer(this.indexBuffer); - - this.renderer = null; -}; diff --git a/src/core/const.js b/src/core/const.js index 2803e5b..aea72ec 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -130,5 +130,25 @@ backgroundColor: 0x000000, clearBeforeRender: true, preserveDrawingBuffer: false + }, + + /** + * Constants that identify shapes, mainly to prevent `instanceof` calls. + * + * @static + * @constant + * @property {object} SHAPES + * @property {object} SHAPES.POLY=0 + * @property {object} SHAPES.RECT=1 + * @property {object} SHAPES.CIRC=2 + * @property {object} SHAPES.ELIP=3 + * @property {object} SHAPES.RREC=4 + */ + SHAPES: { + POLY = 0; + RECT = 1; + CIRC = 2; + ELIP = 3; + RREC = 4; } }; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index bfd59cf..7c0e7c6 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -95,7 +95,7 @@ this.filterArea = null; /** - * cached sin rotation and cos rotation + * cached sin rotation * * @member {number} * @private @@ -103,7 +103,7 @@ this._sr = 0; /** - * cached sin rotation and cos rotation + * cached cos rotation * * @member {number} * @private @@ -400,7 +400,7 @@ DisplayObject.prototype.generateTexture = function (resolution, scaleMode, renderer) { var bounds = this.getLocalBounds(); - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); _tempMatrix.tx = -bounds.x; _tempMatrix.ty = -bounds.y; @@ -474,7 +474,7 @@ var bounds = this.getLocalBounds(); if (!this._cachedSprite) { - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0); //, renderer); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0); this._cachedSprite = new Sprite(renderTexture); this._cachedSprite.worldTransform = this.worldTransform; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 3f6a3b6..69cfdc9 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Circle object can be used to specify a hit area for displayObjects @@ -29,10 +30,11 @@ this.radius = radius || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.CIRC in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.CIRC; } Circle.prototype.constructor = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index fc9df4d..4f2d066 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Ellipse object can be used to specify a hit area for displayObjects @@ -36,10 +37,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.ELIP in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.ELIP; } Ellipse.prototype.constructor = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index 5d4a464..ae89148 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -1,4 +1,5 @@ -var Point = require('../Point'); +var Point = require('../Point'), + CONST = require('../../const'); /** * @class @@ -33,6 +34,13 @@ * @member {Point[]} */ this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + */ + this.type = CONST.SHAPES.POLY; } Polygon.prototype.constructor = Polygon; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index c3454b5..16ac62a 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * the Rectangle object is an area defined by its position, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -34,10 +36,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RECT; } Rectangle.prototype.constructor = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0f2abfc..27d286e 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * The Rounded Rectangle object is an area defined by its position and has nice rounded corners, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -41,10 +43,11 @@ this.radius = radius || 20; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RRECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RREC; } RoundedRectangle.prototype.constructor = RoundedRectangle; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js new file mode 100644 index 0000000..42647c5 --- /dev/null +++ b/src/core/primitives/Graphics.js @@ -0,0 +1,1029 @@ +var core = require('../core'), + GraphicsData = require('./GraphicsData'); + +/** + * 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() { + core.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 {object[]} + * @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 = core.CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {object} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object[]} + * @private + */ + 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 core.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(core.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; + } + } + } +}); + +/** + * 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 core.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 core.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; + + 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 core.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 core.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 core.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 core.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 core.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 core.CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = core.Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + core.CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @method _renderWebGL + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderWebGL = function (renderSession) { + // 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 on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + core.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); + + return; + } + else { + renderSession.spriteBatch.stop(); + renderSession.blendModeManager.setBlendMode(this.blendMode); + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + if (this._filters) { + renderSession.filterManager.pushFilter(this._filterBlock); + } + + // check blend mode + if (this.blendMode !== renderSession.spriteBatch.currentBlendMode) { + renderSession.spriteBatch.currentBlendMode = this.blendMode; + + var blendModeWebGL = blendModesWebGL[renderSession.spriteBatch.currentBlendMode]; + + renderSession.spriteBatch.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); + } + + // check if the webgl graphic needs to be updated + if (this.glDirty) { + this.dirty = true; + this.glDirty = false; + } + + core.WebGLGraphics.renderGraphics(this, renderSession); + + // only render if it has children! + if (this.children.length) { + renderSession.spriteBatch.start(); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderWebGL(renderSession); + } + + renderSession.spriteBatch.stop(); + } + + if (this._filters) { + renderSession.filterManager.popFilter(); + } + + if (this._mask) { + renderSession.maskManager.popMask(this.mask, renderSession); + } + + renderSession.drawCount++; + + renderSession.spriteBatch.start(); + } +}; + +/** + * Renders the object using the Canvas renderer + * + * @method _renderCanvas + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderCanvas = function (renderSession) { + // 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; + + core.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession); + + return; + } + else { + var context = renderSession.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderSession.currentBlendMode) { + renderSession.currentBlendMode = this.blendMode; + context.globalCompositeOperation = blendModesCanvas[renderSession.currentBlendMode]; + } + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + var resolution = renderSession.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + core.CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderCanvas(renderSession); + } + + if (this._mask) { + renderSession.maskManager.popMask(renderSession); + } + } +}; + +/** + * 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 core.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 === core.CONST.SHAPES.RECT || type === core.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 === core.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 === core.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 core.CanvasBuffer(bounds.width, bounds.height); + var texture = core.Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new core.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.. + core.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 === core.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 new file mode 100644 index 0000000..9377ef7 --- /dev/null +++ b/src/core/primitives/GraphicsData.js @@ -0,0 +1,23 @@ +/** + * 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; diff --git a/src/core/primitives/index.js b/src/core/primitives/index.js new file mode 100644 index 0000000..7f02649 --- /dev/null +++ b/src/core/primitives/index.js @@ -0,0 +1,14 @@ +/** + * @file Main export of the PIXI primitives library + * @author Mat Groves + * @copyright 2013-2015 GoodBoyDigital + * @license {@link https://github.com/GoodBoyDigital/pixi.js/blob/master/LICENSE|MIT License} + */ + +/** + * @namespace PIXI + */ +module.exports = { + Graphics: require('./Graphics'), + GraphicsData: require('./GraphicsData') +}; diff --git a/src/core/renderers/canvas/CanvasGraphics.js b/src/core/renderers/canvas/CanvasGraphics.js index 2b1797a..ef7e765 100644 --- a/src/core/renderers/canvas/CanvasGraphics.js +++ b/src/core/renderers/canvas/CanvasGraphics.js @@ -1,4 +1,4 @@ -var Graphics = require('../../../primitives/Graphics'); +var CONST = require('../../const'); /** * A set of functions used by the canvas renderer to draw the primitive graphics data. @@ -30,7 +30,7 @@ context.lineWidth = data.lineWidth; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -61,7 +61,7 @@ context.stroke(); } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { if (data.fillColor || data.fillColor === 0) { context.globalAlpha = data.fillAlpha * worldAlpha; @@ -75,7 +75,7 @@ context.strokeRect(shape.x, shape.y, shape.width, shape.height); } } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); @@ -92,7 +92,7 @@ context.stroke(); } } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas var w = shape.width * 2; @@ -130,7 +130,7 @@ context.stroke(); } } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var rx = shape.x; var ry = shape.y; var width = shape.width; @@ -190,7 +190,7 @@ var data = graphics.graphicsData[i]; var shape = data.shape; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -207,18 +207,18 @@ } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { context.beginPath(); context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); context.closePath(); } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas @@ -245,7 +245,7 @@ context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); context.closePath(); } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var pts = shape.points; var rx = pts[0]; diff --git a/src/core/renderers/webgl/managers/WebGLBlendModeManager.js b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js new file mode 100644 index 0000000..9f2f89a --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js @@ -0,0 +1,37 @@ +var WebGLManager = require('./WebGLManager'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLBlendModeManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; +} + +WebGLBlendModeManager.prototype = Object.create(WebGLManager.prototype); +WebGLBlendModeManager.prototype.constructor = WebGLBlendModeManager; +module.exports = WebGLBlendModeManager; + +/** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as BlendModes.ADD + */ +WebGLBlendModeManager.prototype.setBlendMode = function (blendMode) { + if (this.currentBlendMode === blendMode) { + return false; + } + + this.currentBlendMode = blendMode; + + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; +}; diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js new file mode 100644 index 0000000..176e7cd --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -0,0 +1,446 @@ +var WebGLManager = require('./WebGLManager'), + FilterTexture = require('./FilterTexture'), + PixiShader = require('../shaders/PixiShader'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLFilterManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {any[]} + */ + this.filterStack = []; + + /** + * @member {any[]]} + */ + this.texturePool = []; + + /** + * @member {number} + */ + this.offsetX = 0; + + /** + * @member {number} + */ + this.offsetY = 0; + + // listen for context and update necessary buffers + var self = this; + this.renderer.on('context', function () { + self.texturePool.length = 0; + self.initShaderBuffers(); + }); +} + +WebGLFilterManager.prototype = Object.create(WebGLManager.prototype); +WebGLFilterManager.prototype.constructor = WebGLFilterManager; +module.exports = WebGLFilterManager; + +/** + * @param renderer {WebGLRenderer} + * @param buffer {ArrayBuffer} + */ +WebGLFilterManager.prototype.begin = function (renderer, buffer) { + this.renderer = renderer; + this.defaultShader = renderer.shaderManager.defaultShader; + + this.width = renderer.projection.x * 2; + this.height = -renderer.projection.y * 2; + + this.buffer = buffer; +}; + +/** + * Applies the filter and adds it to the current filter stack. + * + * @param filterBlock {object} the filter that will be pushed to the current filter stack + */ +WebGLFilterManager.prototype.pushFilter = function (filterBlock) { + var gl = this.renderer.gl; + + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds(); + + // filter program + // OPTIMISATION - the first filter is free if its a simple color change? + this.filterStack.push(filterBlock); + + var filter = filterBlock.filterPasses[0]; + + this.offsetX += filterBlock._filterArea.x; + this.offsetY += filterBlock._filterArea.y; + + var texture = this.texturePool.pop(); + if (!texture) { + texture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + else { + texture.resize(this.width, this.height); + } + + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea; + + var padding = filter.padding; + filterArea.x -= padding; + filterArea.y -= padding; + filterArea.width += padding * 2; + filterArea.height += padding * 2; + + // cap filter to screen size.. + if (filterArea.x < 0) { + filterArea.x = 0; + } + + if (filterArea.width > this.width) { + filterArea.width = this.width; + } + + if (filterArea.y < 0) { + filterArea.y = 0; + } + + if (filterArea.height > this.height) { + filterArea.height = this.height; + } + + //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); + + // set view port + gl.viewport(0, 0, filterArea.width, filterArea.height); + + projection.x = filterArea.width/2; + projection.y = -filterArea.height/2; + + offset.x = -filterArea.x; + offset.y = -filterArea.y; + + // update projection + // now restore the regular shader.. + // this.renderer.shaderManager.setShader(this.defaultShader); + //gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); + //gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y); + + gl.colorMask(true, true, true, true); + gl.clearColor(0,0,0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + + filterBlock._glFilterTexture = texture; + +}; + +/** + * Removes the last filter from the filter stack and doesn't return it. + * + */ +WebGLFilterManager.prototype.popFilter = function () { + var gl = this.renderer.gl; + + var filterBlock = this.filterStack.pop(); + var filterArea = filterBlock._filterArea; + var texture = filterBlock._glFilterTexture; + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + if (filterBlock.filterPasses.length > 1) { + gl.viewport(0, 0, filterArea.width, filterArea.height); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = 0; + this.vertexArray[1] = filterArea.height; + + this.vertexArray[2] = filterArea.width; + this.vertexArray[3] = filterArea.height; + + this.vertexArray[4] = 0; + this.vertexArray[5] = 0; + + this.vertexArray[6] = filterArea.width; + this.vertexArray[7] = 0; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + // now set the uvs.. + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + var inputTexture = texture; + var outputTexture = this.texturePool.pop(); + if (!outputTexture) { + outputTexture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + outputTexture.resize(this.width, this.height); + + // need to clear this FBO as it may have some left over elements from a previous filter. + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + gl.clear(gl.COLOR_BUFFER_BIT); + + gl.disable(gl.BLEND); + + for (var i = 0; i < filterBlock.filterPasses.length-1; i++) { + var filterPass = filterBlock.filterPasses[i]; + + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); + + // draw texture.. + //filterPass.applyFilterPass(filterArea.width, filterArea.height); + this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height); + + // swap the textures.. + var temp = inputTexture; + inputTexture = outputTexture; + outputTexture = temp; + } + + gl.enable(gl.BLEND); + + texture = inputTexture; + this.texturePool.push(outputTexture); + } + + var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1]; + + this.offsetX -= filterArea.x; + this.offsetY -= filterArea.y; + + var sizeX = this.width; + var sizeY = this.height; + + var offsetX = 0; + var offsetY = 0; + + var buffer = this.buffer; + + // time to render the filters texture to the previous scene + if (this.filterStack.length === 0) { + gl.colorMask(true, true, true, true);//this.transparent); + } + else { + var currentFilter = this.filterStack[this.filterStack.length-1]; + filterArea = currentFilter._filterArea; + + sizeX = filterArea.width; + sizeY = filterArea.height; + + offsetX = filterArea.x; + offsetY = filterArea.y; + + buffer = currentFilter._glFilterTexture.frameBuffer; + } + + // TODO need to remove these global elements.. + projection.x = sizeX/2; + projection.y = -sizeY/2; + + offset.x = offsetX; + offset.y = offsetY; + + filterArea = filterBlock._filterArea; + + var x = filterArea.x-offsetX; + var y = filterArea.y-offsetY; + + // update the buffers.. + // make sure to flip the y! + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = x; + this.vertexArray[1] = y + filterArea.height; + + this.vertexArray[2] = x + filterArea.width; + this.vertexArray[3] = y + filterArea.height; + + this.vertexArray[4] = x; + this.vertexArray[5] = y; + + this.vertexArray[6] = x + filterArea.width; + this.vertexArray[7] = y; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + gl.viewport(0, 0, sizeX, sizeY); + + // bind the buffer + gl.bindFramebuffer(gl.FRAMEBUFFER, buffer ); + + // set the blend mode! + //gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + // apply! + this.applyFilterPass(filter, filterArea, sizeX, sizeY); + + // now restore the regular shader.. should happen automatically now.. + // this.renderer.shaderManager.setShader(this.defaultShader); + // gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2); + // gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY); + + // return the texture to the pool + this.texturePool.push(texture); + filterBlock._glFilterTexture = null; +}; + + +/** + * Applies the filter to the specified area. + * + * @param filter {AbstractFilter} the filter that needs to be applied + * @param filterArea {Texture} TODO - might need an update + * @param width {number} the horizontal range of the filter + * @param height {number} the vertical range of the filter + */ +WebGLFilterManager.prototype.applyFilterPass = function (filter, filterArea, width, height) { + // use program + var gl = this.renderer.gl; + + var shader = filter.shaders[gl.id]; + + if (!shader) { + shader = new PixiShader(gl); + + shader.fragmentSrc = filter.fragmentSrc; + shader.uniforms = filter.uniforms; + shader.init(); + + filter.shaders[gl.id] = shader; + } + + // set the shader + this.renderer.shaderManager.setShader(shader); + +// gl.useProgram(shader.program); + + gl.uniform2f(shader.projectionVector, width/2, -height/2); + gl.uniform2f(shader.offsetVector, 0,0); + + if (filter.uniforms.dimensions) { + filter.uniforms.dimensions.value[0] = this.width;//width; + filter.uniforms.dimensions.value[1] = this.height;//height; + filter.uniforms.dimensions.value[2] = this.vertexArray[0]; + filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; + } + + shader.syncUniforms(); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + + // draw the filter... + gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); + + this.renderer.drawCount++; +}; + +/** + * Initialises the shader buffers. + * + */ +WebGLFilterManager.prototype.initShaderBuffers = function () { + var gl = this.renderer.gl; + + // create some buffers + this.vertexBuffer = gl.createBuffer(); + this.uvBuffer = gl.createBuffer(); + this.colorBuffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + + // bind and upload the vertexs.. + // keep a reference to the vertexFloatData.. + this.vertexArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW); + + // bind and upload the uv buffer + this.uvArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.uvArray, gl.STATIC_DRAW); + + this.colorArray = new Float32Array([1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.colorArray, gl.STATIC_DRAW); + + // bind and upload the index + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW); + +}; + +/** + * Destroys the filter and removes it from the filter stack. + * + */ +WebGLFilterManager.prototype.destroy = function () { + var gl = this.renderer.gl; + + this.filterStack = null; + + this.offsetX = 0; + this.offsetY = 0; + + // destroy textures + for (var i = 0; i < this.texturePool.length; i++) { + this.texturePool[i].destroy(); + } + + this.texturePool = null; + + //destroy buffers.. + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.uvBuffer); + gl.deleteBuffer(this.colorBuffer); + gl.deleteBuffer(this.indexBuffer); + + this.renderer = null; +}; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js new file mode 100644 index 0000000..9d47b5c --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -0,0 +1,20 @@ +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLManager(renderer) { + /** + * The renderer this manager works for. + * + * @member {WebGLRenderer} + */ + this.renderer = renderer; +} + +WebGLManager.prototype.constructor = WebGLManager; +module.exports = WebGLManager; + +WebGLManager.prototype.destroy = function () { + this.renderer = null; +}; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js new file mode 100644 index 0000000..6d46d85 --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -0,0 +1,41 @@ +var WebGLManager = require('./WebGLManager'), + WebGLGraphics = require('./WebGLGraphics'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLMaskManager(renderer) { + WebGLManager.call(this, renderer); +} + +WebGLMaskManager.prototype = Object.create(WebGLManager.prototype); +WebGLMaskManager.prototype.constructor = WebGLMaskManager; +module.exports = WebGLMaskManager; + +/** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {any[]} + */ +WebGLMaskManager.prototype.pushMask = function (maskData) { + if (maskData.dirty) { + WebGLGraphics.updateGraphics(maskData, this.renderer.gl); + } + + if (!maskData._webGL[this.renderer.gl.id].data.length) { + return; + } + + this.renderer.stencilManager.pushStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); +}; + +/** + * Removes the last filter from the filter stack and doesn't return it. + * + * @param maskData {any[]} + */ +WebGLMaskManager.prototype.popMask = function (maskData) { + this.renderer.stencilManager.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 new file mode 100644 index 0000000..d02d95d --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -0,0 +1,172 @@ +var WebGLManager = require('./WebGLManager'), + PrimitiveShader = require('../shaders/PrimitiveShader'), + ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), + PixiShader = require('../shaders/PixiShader'), + PixiFastShader = require('../shaders/PixiFastShader'), + StripShader = require('../shaders/StripShader'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLShaderManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {number} + */ + this.maxAttibs = 10; + + /** + * @member {any[]} + */ + this.attribState = []; + + /** + * @member {any[]} + */ + this.tempAttribState = []; + + for (var i = 0; i < this.maxAttibs; i++) { + this.attribState[i] = false; + } + + /** + * @member {any[]} + */ + this.stack = []; + + /** + * @member {number} + * @private + */ + this._currentId = -1; + + /** + * @member {Shader} + * @private + */ + 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; + + // listen for context and update necessary shaders + var self = this; + this.renderer.on('context', function (gl) { + // 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 PixiShader(gl); + + // this shader is used for the fast sprite rendering + self.fastShader = new PixiFastShader(gl); + + // the next one is used for rendering triangle strips + self.stripShader = new StripShader(gl); + + self.setShader(self.defaultShader); + }); +} + +WebGLShaderManager.prototype = Object.create(WebGLManager.prototype); +WebGLShaderManager.prototype.constructor = WebGLShaderManager; +module.exports = WebGLShaderManager; + +/** + * Takes the attributes given in parameters. + * + * @param attribs {Array} attribs + */ +WebGLShaderManager.prototype.setAttribs = function (attribs) { + // reset temp state + var i; + + for (i = 0; i < this.tempAttribState.length; i++) { + this.tempAttribState[i] = false; + } + + // set the new attribs + for (var a in attribs) { + this.tempAttribState[attribs[a]] = true; + } + + var gl = this.renderer.gl; + + for (i = 0; i < this.attribState.length; i++) { + if (this.attribState[i] !== this.tempAttribState[i]) { + this.attribState[i] = this.tempAttribState[i]; + + if (this.attribState[i]) { + gl.enableVertexAttribArray(i); + } + else { + gl.disableVertexAttribArray(i); + } + } + } +}; + +/** + * Sets the current shader. + * + * @param shader {Any} + */ +WebGLShaderManager.prototype.setShader = function (shader) { + if (this._currentId === shader.uuid) { + return false; + } + + this._currentId = shader.uuid; + + this.currentShader = shader; + + this.renderer.gl.useProgram(shader.program); + this.setAttribs(shader.attributes); + + return true; +}; + +/** + * Destroys this object. + * + */ +WebGLShaderManager.prototype.destroy = function () { + 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/managers/WebGLStencilManager.js b/src/core/renderers/webgl/managers/WebGLStencilManager.js new file mode 100644 index 0000000..0bedae6 --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLStencilManager.js @@ -0,0 +1,256 @@ +var WebGLManager = require('./WebGLManager'), + utils = require('../../../utils'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLStencilManager(renderer) { + WebGLManager.call(this, renderer); + + this.stencilStack = []; + this.reverse = true; + this.count = 0; +} + +WebGLStencilManager.prototype = Object.create(WebGLManager.prototype); +WebGLStencilManager.prototype.constructor = WebGLStencilManager; +module.exports = WebGLStencilManager; + +/** + * Applies the Mask and adds it to the current filter stack. + * + * @param graphics {Graphics} + * @param webGLData {any[]} + */ +WebGLStencilManager.prototype.pushStencil = function (graphics, webGLData) { + var gl = this.renderer.gl; + + this.bindGraphics(graphics, webGLData, this.renderer); + + if (this.stencilStack.length === 0) { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + this.reverse = true; + this.count = 0; + } + + this.stencilStack.push(webGLData); + + var level = this.count; + + gl.colorMask(false, false, false, false); + + gl.stencilFunc(gl.ALWAYS,0,0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); + + // draw the triangle strip! + + if (webGLData.mode === 1) { + gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + + // draw a quad to increment.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + } + + this.reverse = !this.reverse; + } + else { + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + } + } + + gl.colorMask(true, true, true, true); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + + this.count++; +}; + +/** + * TODO this does not belong here! + * + * @param graphics {Graphics} + * @param webGLData {Array} + */ +WebGLStencilManager.prototype.bindGraphics = function (graphics, webGLData) { + //if (this._currentGraphics === graphics)return; + this._currentGraphics = graphics; + + var gl = this.renderer.gl; + + // bind the graphics object.. + var projection = this.renderer.projection, + offset = this.renderer.offset, + shader;// = this.renderer.shaderManager.primitiveShader; + + if (webGLData.mode === 1) { + shader = this.renderer.shaderManager.complexPrimitiveShader; + + this.renderer.shaderManager.setShader(shader); + + gl.uniform1f(shader.flipY, this.renderer.flipY); + + gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); + + gl.uniform2f(shader.projectionVector, projection.x, -projection.y); + gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); + + gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); + gl.uniform3fv(shader.color, webGLData.color); + + gl.uniform1f(shader.alpha, graphics.worldAlpha * webGLData.alpha); + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 2, 0); + + + // now do the rest.. + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + } + else { + //this.renderer.shaderManager.activatePrimitiveShader(); + shader = this.renderer.shaderManager.primitiveShader; + this.renderer.shaderManager.setShader( shader ); + + gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); + + gl.uniform1f(shader.flipY, this.renderer.flipY); + gl.uniform2f(shader.projectionVector, projection.x, -projection.y); + gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); + + gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); + + gl.uniform1f(shader.alpha, graphics.worldAlpha); + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); + gl.vertexAttribPointer(shader.colorAttribute, 4, gl.FLOAT, false,4 * 6, 2 * 4); + + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + } +}; + +/** + * @param graphics {Graphics} + * @param webGLData {Array} + */ +WebGLStencilManager.prototype.popStencil = function (graphics, webGLData) { + var gl = this.renderer.gl; + + this.stencilStack.pop(); + + this.count--; + + if (this.stencilStack.length === 0) { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + + } + else { + + var level = this.count; + + this.bindGraphics(graphics, webGLData, this.renderer); + + gl.colorMask(false, false, false, false); + + if (webGLData.mode === 1) { + this.reverse = !this.reverse; + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + + // draw a quad to increment.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + gl.stencilFunc(gl.ALWAYS,0,0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); + + // draw the triangle strip! + gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + } + + } + else { + // console.log("<<>>") + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + } + } + + gl.colorMask(true, true, true, true); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + + + } +}; + +/** + * Destroys the mask stack. + * + */ +WebGLStencilManager.prototype.destroy = function () { + this.renderer = null; + this.stencilStack = null; +}; diff --git a/src/core/renderers/webgl/utils/WebGLBlendModeManager.js b/src/core/renderers/webgl/utils/WebGLBlendModeManager.js deleted file mode 100644 index 9f2f89a..0000000 --- a/src/core/renderers/webgl/utils/WebGLBlendModeManager.js +++ /dev/null @@ -1,37 +0,0 @@ -var WebGLManager = require('./WebGLManager'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLBlendModeManager(renderer) { - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -WebGLBlendModeManager.prototype = Object.create(WebGLManager.prototype); -WebGLBlendModeManager.prototype.constructor = WebGLBlendModeManager; -module.exports = WebGLBlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as BlendModes.ADD - */ -WebGLBlendModeManager.prototype.setBlendMode = function (blendMode) { - if (this.currentBlendMode === blendMode) { - return false; - } - - this.currentBlendMode = blendMode; - - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); - - return true; -}; diff --git a/src/core/renderers/webgl/utils/WebGLFilterManager.js b/src/core/renderers/webgl/utils/WebGLFilterManager.js deleted file mode 100644 index 176e7cd..0000000 --- a/src/core/renderers/webgl/utils/WebGLFilterManager.js +++ /dev/null @@ -1,446 +0,0 @@ -var WebGLManager = require('./WebGLManager'), - FilterTexture = require('./FilterTexture'), - PixiShader = require('../shaders/PixiShader'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLFilterManager(renderer) { - WebGLManager.call(this, renderer); - - /** - * @member {any[]} - */ - this.filterStack = []; - - /** - * @member {any[]]} - */ - this.texturePool = []; - - /** - * @member {number} - */ - this.offsetX = 0; - - /** - * @member {number} - */ - this.offsetY = 0; - - // listen for context and update necessary buffers - var self = this; - this.renderer.on('context', function () { - self.texturePool.length = 0; - self.initShaderBuffers(); - }); -} - -WebGLFilterManager.prototype = Object.create(WebGLManager.prototype); -WebGLFilterManager.prototype.constructor = WebGLFilterManager; -module.exports = WebGLFilterManager; - -/** - * @param renderer {WebGLRenderer} - * @param buffer {ArrayBuffer} - */ -WebGLFilterManager.prototype.begin = function (renderer, buffer) { - this.renderer = renderer; - this.defaultShader = renderer.shaderManager.defaultShader; - - this.width = renderer.projection.x * 2; - this.height = -renderer.projection.y * 2; - - this.buffer = buffer; -}; - -/** - * Applies the filter and adds it to the current filter stack. - * - * @param filterBlock {object} the filter that will be pushed to the current filter stack - */ -WebGLFilterManager.prototype.pushFilter = function (filterBlock) { - var gl = this.renderer.gl; - - var projection = this.renderer.projection; - var offset = this.renderer.offset; - - filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds(); - - // filter program - // OPTIMISATION - the first filter is free if its a simple color change? - this.filterStack.push(filterBlock); - - var filter = filterBlock.filterPasses[0]; - - this.offsetX += filterBlock._filterArea.x; - this.offsetY += filterBlock._filterArea.y; - - var texture = this.texturePool.pop(); - if (!texture) { - texture = new FilterTexture(this.renderer.gl, this.width, this.height); - } - else { - texture.resize(this.width, this.height); - } - - gl.bindTexture(gl.TEXTURE_2D, texture.texture); - - var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea; - - var padding = filter.padding; - filterArea.x -= padding; - filterArea.y -= padding; - filterArea.width += padding * 2; - filterArea.height += padding * 2; - - // cap filter to screen size.. - if (filterArea.x < 0) { - filterArea.x = 0; - } - - if (filterArea.width > this.width) { - filterArea.width = this.width; - } - - if (filterArea.y < 0) { - filterArea.y = 0; - } - - if (filterArea.height > this.height) { - filterArea.height = this.height; - } - - //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); - - // set view port - gl.viewport(0, 0, filterArea.width, filterArea.height); - - projection.x = filterArea.width/2; - projection.y = -filterArea.height/2; - - offset.x = -filterArea.x; - offset.y = -filterArea.y; - - // update projection - // now restore the regular shader.. - // this.renderer.shaderManager.setShader(this.defaultShader); - //gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); - //gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y); - - gl.colorMask(true, true, true, true); - gl.clearColor(0,0,0, 0); - gl.clear(gl.COLOR_BUFFER_BIT); - - filterBlock._glFilterTexture = texture; - -}; - -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -WebGLFilterManager.prototype.popFilter = function () { - var gl = this.renderer.gl; - - var filterBlock = this.filterStack.pop(); - var filterArea = filterBlock._filterArea; - var texture = filterBlock._glFilterTexture; - var projection = this.renderer.projection; - var offset = this.renderer.offset; - - if (filterBlock.filterPasses.length > 1) { - gl.viewport(0, 0, filterArea.width, filterArea.height); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - - this.vertexArray[0] = 0; - this.vertexArray[1] = filterArea.height; - - this.vertexArray[2] = filterArea.width; - this.vertexArray[3] = filterArea.height; - - this.vertexArray[4] = 0; - this.vertexArray[5] = 0; - - this.vertexArray[6] = filterArea.width; - this.vertexArray[7] = 0; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - // now set the uvs.. - this.uvArray[2] = filterArea.width/this.width; - this.uvArray[5] = filterArea.height/this.height; - this.uvArray[6] = filterArea.width/this.width; - this.uvArray[7] = filterArea.height/this.height; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); - - var inputTexture = texture; - var outputTexture = this.texturePool.pop(); - if (!outputTexture) { - outputTexture = new FilterTexture(this.renderer.gl, this.width, this.height); - } - outputTexture.resize(this.width, this.height); - - // need to clear this FBO as it may have some left over elements from a previous filter. - gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); - gl.clear(gl.COLOR_BUFFER_BIT); - - gl.disable(gl.BLEND); - - for (var i = 0; i < filterBlock.filterPasses.length-1; i++) { - var filterPass = filterBlock.filterPasses[i]; - - gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); - - // set texture - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); - - // draw texture.. - //filterPass.applyFilterPass(filterArea.width, filterArea.height); - this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height); - - // swap the textures.. - var temp = inputTexture; - inputTexture = outputTexture; - outputTexture = temp; - } - - gl.enable(gl.BLEND); - - texture = inputTexture; - this.texturePool.push(outputTexture); - } - - var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1]; - - this.offsetX -= filterArea.x; - this.offsetY -= filterArea.y; - - var sizeX = this.width; - var sizeY = this.height; - - var offsetX = 0; - var offsetY = 0; - - var buffer = this.buffer; - - // time to render the filters texture to the previous scene - if (this.filterStack.length === 0) { - gl.colorMask(true, true, true, true);//this.transparent); - } - else { - var currentFilter = this.filterStack[this.filterStack.length-1]; - filterArea = currentFilter._filterArea; - - sizeX = filterArea.width; - sizeY = filterArea.height; - - offsetX = filterArea.x; - offsetY = filterArea.y; - - buffer = currentFilter._glFilterTexture.frameBuffer; - } - - // TODO need to remove these global elements.. - projection.x = sizeX/2; - projection.y = -sizeY/2; - - offset.x = offsetX; - offset.y = offsetY; - - filterArea = filterBlock._filterArea; - - var x = filterArea.x-offsetX; - var y = filterArea.y-offsetY; - - // update the buffers.. - // make sure to flip the y! - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - - this.vertexArray[0] = x; - this.vertexArray[1] = y + filterArea.height; - - this.vertexArray[2] = x + filterArea.width; - this.vertexArray[3] = y + filterArea.height; - - this.vertexArray[4] = x; - this.vertexArray[5] = y; - - this.vertexArray[6] = x + filterArea.width; - this.vertexArray[7] = y; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - - this.uvArray[2] = filterArea.width/this.width; - this.uvArray[5] = filterArea.height/this.height; - this.uvArray[6] = filterArea.width/this.width; - this.uvArray[7] = filterArea.height/this.height; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); - - gl.viewport(0, 0, sizeX, sizeY); - - // bind the buffer - gl.bindFramebuffer(gl.FRAMEBUFFER, buffer ); - - // set the blend mode! - //gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) - - // set texture - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, texture.texture); - - // apply! - this.applyFilterPass(filter, filterArea, sizeX, sizeY); - - // now restore the regular shader.. should happen automatically now.. - // this.renderer.shaderManager.setShader(this.defaultShader); - // gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2); - // gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY); - - // return the texture to the pool - this.texturePool.push(texture); - filterBlock._glFilterTexture = null; -}; - - -/** - * Applies the filter to the specified area. - * - * @param filter {AbstractFilter} the filter that needs to be applied - * @param filterArea {Texture} TODO - might need an update - * @param width {number} the horizontal range of the filter - * @param height {number} the vertical range of the filter - */ -WebGLFilterManager.prototype.applyFilterPass = function (filter, filterArea, width, height) { - // use program - var gl = this.renderer.gl; - - var shader = filter.shaders[gl.id]; - - if (!shader) { - shader = new PixiShader(gl); - - shader.fragmentSrc = filter.fragmentSrc; - shader.uniforms = filter.uniforms; - shader.init(); - - filter.shaders[gl.id] = shader; - } - - // set the shader - this.renderer.shaderManager.setShader(shader); - -// gl.useProgram(shader.program); - - gl.uniform2f(shader.projectionVector, width/2, -height/2); - gl.uniform2f(shader.offsetVector, 0,0); - - if (filter.uniforms.dimensions) { - filter.uniforms.dimensions.value[0] = this.width;//width; - filter.uniforms.dimensions.value[1] = this.height;//height; - filter.uniforms.dimensions.value[2] = this.vertexArray[0]; - filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; - } - - shader.syncUniforms(); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); - gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - - // draw the filter... - gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); - - this.renderer.drawCount++; -}; - -/** - * Initialises the shader buffers. - * - */ -WebGLFilterManager.prototype.initShaderBuffers = function () { - var gl = this.renderer.gl; - - // create some buffers - this.vertexBuffer = gl.createBuffer(); - this.uvBuffer = gl.createBuffer(); - this.colorBuffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - - // bind and upload the vertexs.. - // keep a reference to the vertexFloatData.. - this.vertexArray = new Float32Array([0.0, 0.0, - 1.0, 0.0, - 0.0, 1.0, - 1.0, 1.0]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW); - - // bind and upload the uv buffer - this.uvArray = new Float32Array([0.0, 0.0, - 1.0, 0.0, - 0.0, 1.0, - 1.0, 1.0]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.uvArray, gl.STATIC_DRAW); - - this.colorArray = new Float32Array([1.0, 0xFFFFFF, - 1.0, 0xFFFFFF, - 1.0, 0xFFFFFF, - 1.0, 0xFFFFFF]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.colorArray, gl.STATIC_DRAW); - - // bind and upload the index - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW); - -}; - -/** - * Destroys the filter and removes it from the filter stack. - * - */ -WebGLFilterManager.prototype.destroy = function () { - var gl = this.renderer.gl; - - this.filterStack = null; - - this.offsetX = 0; - this.offsetY = 0; - - // destroy textures - for (var i = 0; i < this.texturePool.length; i++) { - this.texturePool[i].destroy(); - } - - this.texturePool = null; - - //destroy buffers.. - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.uvBuffer); - gl.deleteBuffer(this.colorBuffer); - gl.deleteBuffer(this.indexBuffer); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/WebGLGraphics.js b/src/core/renderers/webgl/utils/WebGLGraphics.js index 09f540f..1d1fe40 100644 --- a/src/core/renderers/webgl/utils/WebGLGraphics.js +++ b/src/core/renderers/webgl/utils/WebGLGraphics.js @@ -1,6 +1,5 @@ var utils = require('../../../utils'), math = require('../../../math'), - Graphics = require('../../../../primitives/Graphics'), WebGLGraphicsData = require('./WebGLGraphicsData'); /** @@ -122,7 +121,7 @@ for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) { var data = graphics.graphicsData[i]; - if (data.type === Graphics.POLY) { + 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) { @@ -164,13 +163,13 @@ else { webGLData = WebGLGraphics.switchMode(webGL, 0); - if (data.type === Graphics.RECT) { + if (data.type === CONST.SHAPES.RECT) { WebGLGraphics.buildRectangle(data, webGLData); } - else if (data.type === Graphics.CIRC || data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) { WebGLGraphics.buildCircle(data, webGLData); } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { WebGLGraphics.buildRoundedRectangle(data, webGLData); } } @@ -415,7 +414,7 @@ var height; // TODO - bit hacky?? - if (graphicsData.type === Graphics.CIRC) { + if (graphicsData.type === CONST.SHAPES.CIRC) { width = circleData.radius; height = circleData.radius; } diff --git a/src/core/const.js b/src/core/const.js index 2803e5b..aea72ec 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -130,5 +130,25 @@ backgroundColor: 0x000000, clearBeforeRender: true, preserveDrawingBuffer: false + }, + + /** + * Constants that identify shapes, mainly to prevent `instanceof` calls. + * + * @static + * @constant + * @property {object} SHAPES + * @property {object} SHAPES.POLY=0 + * @property {object} SHAPES.RECT=1 + * @property {object} SHAPES.CIRC=2 + * @property {object} SHAPES.ELIP=3 + * @property {object} SHAPES.RREC=4 + */ + SHAPES: { + POLY = 0; + RECT = 1; + CIRC = 2; + ELIP = 3; + RREC = 4; } }; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index bfd59cf..7c0e7c6 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -95,7 +95,7 @@ this.filterArea = null; /** - * cached sin rotation and cos rotation + * cached sin rotation * * @member {number} * @private @@ -103,7 +103,7 @@ this._sr = 0; /** - * cached sin rotation and cos rotation + * cached cos rotation * * @member {number} * @private @@ -400,7 +400,7 @@ DisplayObject.prototype.generateTexture = function (resolution, scaleMode, renderer) { var bounds = this.getLocalBounds(); - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); _tempMatrix.tx = -bounds.x; _tempMatrix.ty = -bounds.y; @@ -474,7 +474,7 @@ var bounds = this.getLocalBounds(); if (!this._cachedSprite) { - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0); //, renderer); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0); this._cachedSprite = new Sprite(renderTexture); this._cachedSprite.worldTransform = this.worldTransform; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 3f6a3b6..69cfdc9 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Circle object can be used to specify a hit area for displayObjects @@ -29,10 +30,11 @@ this.radius = radius || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.CIRC in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.CIRC; } Circle.prototype.constructor = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index fc9df4d..4f2d066 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Ellipse object can be used to specify a hit area for displayObjects @@ -36,10 +37,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.ELIP in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.ELIP; } Ellipse.prototype.constructor = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index 5d4a464..ae89148 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -1,4 +1,5 @@ -var Point = require('../Point'); +var Point = require('../Point'), + CONST = require('../../const'); /** * @class @@ -33,6 +34,13 @@ * @member {Point[]} */ this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + */ + this.type = CONST.SHAPES.POLY; } Polygon.prototype.constructor = Polygon; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index c3454b5..16ac62a 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * the Rectangle object is an area defined by its position, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -34,10 +36,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RECT; } Rectangle.prototype.constructor = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0f2abfc..27d286e 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * The Rounded Rectangle object is an area defined by its position and has nice rounded corners, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -41,10 +43,11 @@ this.radius = radius || 20; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RRECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RREC; } RoundedRectangle.prototype.constructor = RoundedRectangle; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js new file mode 100644 index 0000000..42647c5 --- /dev/null +++ b/src/core/primitives/Graphics.js @@ -0,0 +1,1029 @@ +var core = require('../core'), + GraphicsData = require('./GraphicsData'); + +/** + * 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() { + core.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 {object[]} + * @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 = core.CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {object} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object[]} + * @private + */ + 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 core.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(core.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; + } + } + } +}); + +/** + * 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 core.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 core.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; + + 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 core.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 core.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 core.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 core.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 core.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 core.CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = core.Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + core.CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @method _renderWebGL + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderWebGL = function (renderSession) { + // 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 on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + core.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); + + return; + } + else { + renderSession.spriteBatch.stop(); + renderSession.blendModeManager.setBlendMode(this.blendMode); + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + if (this._filters) { + renderSession.filterManager.pushFilter(this._filterBlock); + } + + // check blend mode + if (this.blendMode !== renderSession.spriteBatch.currentBlendMode) { + renderSession.spriteBatch.currentBlendMode = this.blendMode; + + var blendModeWebGL = blendModesWebGL[renderSession.spriteBatch.currentBlendMode]; + + renderSession.spriteBatch.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); + } + + // check if the webgl graphic needs to be updated + if (this.glDirty) { + this.dirty = true; + this.glDirty = false; + } + + core.WebGLGraphics.renderGraphics(this, renderSession); + + // only render if it has children! + if (this.children.length) { + renderSession.spriteBatch.start(); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderWebGL(renderSession); + } + + renderSession.spriteBatch.stop(); + } + + if (this._filters) { + renderSession.filterManager.popFilter(); + } + + if (this._mask) { + renderSession.maskManager.popMask(this.mask, renderSession); + } + + renderSession.drawCount++; + + renderSession.spriteBatch.start(); + } +}; + +/** + * Renders the object using the Canvas renderer + * + * @method _renderCanvas + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderCanvas = function (renderSession) { + // 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; + + core.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession); + + return; + } + else { + var context = renderSession.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderSession.currentBlendMode) { + renderSession.currentBlendMode = this.blendMode; + context.globalCompositeOperation = blendModesCanvas[renderSession.currentBlendMode]; + } + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + var resolution = renderSession.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + core.CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderCanvas(renderSession); + } + + if (this._mask) { + renderSession.maskManager.popMask(renderSession); + } + } +}; + +/** + * 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 core.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 === core.CONST.SHAPES.RECT || type === core.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 === core.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 === core.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 core.CanvasBuffer(bounds.width, bounds.height); + var texture = core.Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new core.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.. + core.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 === core.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 new file mode 100644 index 0000000..9377ef7 --- /dev/null +++ b/src/core/primitives/GraphicsData.js @@ -0,0 +1,23 @@ +/** + * 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; diff --git a/src/core/primitives/index.js b/src/core/primitives/index.js new file mode 100644 index 0000000..7f02649 --- /dev/null +++ b/src/core/primitives/index.js @@ -0,0 +1,14 @@ +/** + * @file Main export of the PIXI primitives library + * @author Mat Groves + * @copyright 2013-2015 GoodBoyDigital + * @license {@link https://github.com/GoodBoyDigital/pixi.js/blob/master/LICENSE|MIT License} + */ + +/** + * @namespace PIXI + */ +module.exports = { + Graphics: require('./Graphics'), + GraphicsData: require('./GraphicsData') +}; diff --git a/src/core/renderers/canvas/CanvasGraphics.js b/src/core/renderers/canvas/CanvasGraphics.js index 2b1797a..ef7e765 100644 --- a/src/core/renderers/canvas/CanvasGraphics.js +++ b/src/core/renderers/canvas/CanvasGraphics.js @@ -1,4 +1,4 @@ -var Graphics = require('../../../primitives/Graphics'); +var CONST = require('../../const'); /** * A set of functions used by the canvas renderer to draw the primitive graphics data. @@ -30,7 +30,7 @@ context.lineWidth = data.lineWidth; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -61,7 +61,7 @@ context.stroke(); } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { if (data.fillColor || data.fillColor === 0) { context.globalAlpha = data.fillAlpha * worldAlpha; @@ -75,7 +75,7 @@ context.strokeRect(shape.x, shape.y, shape.width, shape.height); } } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); @@ -92,7 +92,7 @@ context.stroke(); } } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas var w = shape.width * 2; @@ -130,7 +130,7 @@ context.stroke(); } } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var rx = shape.x; var ry = shape.y; var width = shape.width; @@ -190,7 +190,7 @@ var data = graphics.graphicsData[i]; var shape = data.shape; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -207,18 +207,18 @@ } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { context.beginPath(); context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); context.closePath(); } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas @@ -245,7 +245,7 @@ context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); context.closePath(); } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var pts = shape.points; var rx = pts[0]; diff --git a/src/core/renderers/webgl/managers/WebGLBlendModeManager.js b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js new file mode 100644 index 0000000..9f2f89a --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js @@ -0,0 +1,37 @@ +var WebGLManager = require('./WebGLManager'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLBlendModeManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; +} + +WebGLBlendModeManager.prototype = Object.create(WebGLManager.prototype); +WebGLBlendModeManager.prototype.constructor = WebGLBlendModeManager; +module.exports = WebGLBlendModeManager; + +/** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as BlendModes.ADD + */ +WebGLBlendModeManager.prototype.setBlendMode = function (blendMode) { + if (this.currentBlendMode === blendMode) { + return false; + } + + this.currentBlendMode = blendMode; + + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; +}; diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js new file mode 100644 index 0000000..176e7cd --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -0,0 +1,446 @@ +var WebGLManager = require('./WebGLManager'), + FilterTexture = require('./FilterTexture'), + PixiShader = require('../shaders/PixiShader'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLFilterManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {any[]} + */ + this.filterStack = []; + + /** + * @member {any[]]} + */ + this.texturePool = []; + + /** + * @member {number} + */ + this.offsetX = 0; + + /** + * @member {number} + */ + this.offsetY = 0; + + // listen for context and update necessary buffers + var self = this; + this.renderer.on('context', function () { + self.texturePool.length = 0; + self.initShaderBuffers(); + }); +} + +WebGLFilterManager.prototype = Object.create(WebGLManager.prototype); +WebGLFilterManager.prototype.constructor = WebGLFilterManager; +module.exports = WebGLFilterManager; + +/** + * @param renderer {WebGLRenderer} + * @param buffer {ArrayBuffer} + */ +WebGLFilterManager.prototype.begin = function (renderer, buffer) { + this.renderer = renderer; + this.defaultShader = renderer.shaderManager.defaultShader; + + this.width = renderer.projection.x * 2; + this.height = -renderer.projection.y * 2; + + this.buffer = buffer; +}; + +/** + * Applies the filter and adds it to the current filter stack. + * + * @param filterBlock {object} the filter that will be pushed to the current filter stack + */ +WebGLFilterManager.prototype.pushFilter = function (filterBlock) { + var gl = this.renderer.gl; + + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds(); + + // filter program + // OPTIMISATION - the first filter is free if its a simple color change? + this.filterStack.push(filterBlock); + + var filter = filterBlock.filterPasses[0]; + + this.offsetX += filterBlock._filterArea.x; + this.offsetY += filterBlock._filterArea.y; + + var texture = this.texturePool.pop(); + if (!texture) { + texture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + else { + texture.resize(this.width, this.height); + } + + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea; + + var padding = filter.padding; + filterArea.x -= padding; + filterArea.y -= padding; + filterArea.width += padding * 2; + filterArea.height += padding * 2; + + // cap filter to screen size.. + if (filterArea.x < 0) { + filterArea.x = 0; + } + + if (filterArea.width > this.width) { + filterArea.width = this.width; + } + + if (filterArea.y < 0) { + filterArea.y = 0; + } + + if (filterArea.height > this.height) { + filterArea.height = this.height; + } + + //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); + + // set view port + gl.viewport(0, 0, filterArea.width, filterArea.height); + + projection.x = filterArea.width/2; + projection.y = -filterArea.height/2; + + offset.x = -filterArea.x; + offset.y = -filterArea.y; + + // update projection + // now restore the regular shader.. + // this.renderer.shaderManager.setShader(this.defaultShader); + //gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); + //gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y); + + gl.colorMask(true, true, true, true); + gl.clearColor(0,0,0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + + filterBlock._glFilterTexture = texture; + +}; + +/** + * Removes the last filter from the filter stack and doesn't return it. + * + */ +WebGLFilterManager.prototype.popFilter = function () { + var gl = this.renderer.gl; + + var filterBlock = this.filterStack.pop(); + var filterArea = filterBlock._filterArea; + var texture = filterBlock._glFilterTexture; + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + if (filterBlock.filterPasses.length > 1) { + gl.viewport(0, 0, filterArea.width, filterArea.height); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = 0; + this.vertexArray[1] = filterArea.height; + + this.vertexArray[2] = filterArea.width; + this.vertexArray[3] = filterArea.height; + + this.vertexArray[4] = 0; + this.vertexArray[5] = 0; + + this.vertexArray[6] = filterArea.width; + this.vertexArray[7] = 0; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + // now set the uvs.. + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + var inputTexture = texture; + var outputTexture = this.texturePool.pop(); + if (!outputTexture) { + outputTexture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + outputTexture.resize(this.width, this.height); + + // need to clear this FBO as it may have some left over elements from a previous filter. + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + gl.clear(gl.COLOR_BUFFER_BIT); + + gl.disable(gl.BLEND); + + for (var i = 0; i < filterBlock.filterPasses.length-1; i++) { + var filterPass = filterBlock.filterPasses[i]; + + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); + + // draw texture.. + //filterPass.applyFilterPass(filterArea.width, filterArea.height); + this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height); + + // swap the textures.. + var temp = inputTexture; + inputTexture = outputTexture; + outputTexture = temp; + } + + gl.enable(gl.BLEND); + + texture = inputTexture; + this.texturePool.push(outputTexture); + } + + var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1]; + + this.offsetX -= filterArea.x; + this.offsetY -= filterArea.y; + + var sizeX = this.width; + var sizeY = this.height; + + var offsetX = 0; + var offsetY = 0; + + var buffer = this.buffer; + + // time to render the filters texture to the previous scene + if (this.filterStack.length === 0) { + gl.colorMask(true, true, true, true);//this.transparent); + } + else { + var currentFilter = this.filterStack[this.filterStack.length-1]; + filterArea = currentFilter._filterArea; + + sizeX = filterArea.width; + sizeY = filterArea.height; + + offsetX = filterArea.x; + offsetY = filterArea.y; + + buffer = currentFilter._glFilterTexture.frameBuffer; + } + + // TODO need to remove these global elements.. + projection.x = sizeX/2; + projection.y = -sizeY/2; + + offset.x = offsetX; + offset.y = offsetY; + + filterArea = filterBlock._filterArea; + + var x = filterArea.x-offsetX; + var y = filterArea.y-offsetY; + + // update the buffers.. + // make sure to flip the y! + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = x; + this.vertexArray[1] = y + filterArea.height; + + this.vertexArray[2] = x + filterArea.width; + this.vertexArray[3] = y + filterArea.height; + + this.vertexArray[4] = x; + this.vertexArray[5] = y; + + this.vertexArray[6] = x + filterArea.width; + this.vertexArray[7] = y; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + gl.viewport(0, 0, sizeX, sizeY); + + // bind the buffer + gl.bindFramebuffer(gl.FRAMEBUFFER, buffer ); + + // set the blend mode! + //gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + // apply! + this.applyFilterPass(filter, filterArea, sizeX, sizeY); + + // now restore the regular shader.. should happen automatically now.. + // this.renderer.shaderManager.setShader(this.defaultShader); + // gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2); + // gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY); + + // return the texture to the pool + this.texturePool.push(texture); + filterBlock._glFilterTexture = null; +}; + + +/** + * Applies the filter to the specified area. + * + * @param filter {AbstractFilter} the filter that needs to be applied + * @param filterArea {Texture} TODO - might need an update + * @param width {number} the horizontal range of the filter + * @param height {number} the vertical range of the filter + */ +WebGLFilterManager.prototype.applyFilterPass = function (filter, filterArea, width, height) { + // use program + var gl = this.renderer.gl; + + var shader = filter.shaders[gl.id]; + + if (!shader) { + shader = new PixiShader(gl); + + shader.fragmentSrc = filter.fragmentSrc; + shader.uniforms = filter.uniforms; + shader.init(); + + filter.shaders[gl.id] = shader; + } + + // set the shader + this.renderer.shaderManager.setShader(shader); + +// gl.useProgram(shader.program); + + gl.uniform2f(shader.projectionVector, width/2, -height/2); + gl.uniform2f(shader.offsetVector, 0,0); + + if (filter.uniforms.dimensions) { + filter.uniforms.dimensions.value[0] = this.width;//width; + filter.uniforms.dimensions.value[1] = this.height;//height; + filter.uniforms.dimensions.value[2] = this.vertexArray[0]; + filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; + } + + shader.syncUniforms(); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + + // draw the filter... + gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); + + this.renderer.drawCount++; +}; + +/** + * Initialises the shader buffers. + * + */ +WebGLFilterManager.prototype.initShaderBuffers = function () { + var gl = this.renderer.gl; + + // create some buffers + this.vertexBuffer = gl.createBuffer(); + this.uvBuffer = gl.createBuffer(); + this.colorBuffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + + // bind and upload the vertexs.. + // keep a reference to the vertexFloatData.. + this.vertexArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW); + + // bind and upload the uv buffer + this.uvArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.uvArray, gl.STATIC_DRAW); + + this.colorArray = new Float32Array([1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.colorArray, gl.STATIC_DRAW); + + // bind and upload the index + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW); + +}; + +/** + * Destroys the filter and removes it from the filter stack. + * + */ +WebGLFilterManager.prototype.destroy = function () { + var gl = this.renderer.gl; + + this.filterStack = null; + + this.offsetX = 0; + this.offsetY = 0; + + // destroy textures + for (var i = 0; i < this.texturePool.length; i++) { + this.texturePool[i].destroy(); + } + + this.texturePool = null; + + //destroy buffers.. + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.uvBuffer); + gl.deleteBuffer(this.colorBuffer); + gl.deleteBuffer(this.indexBuffer); + + this.renderer = null; +}; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js new file mode 100644 index 0000000..9d47b5c --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -0,0 +1,20 @@ +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLManager(renderer) { + /** + * The renderer this manager works for. + * + * @member {WebGLRenderer} + */ + this.renderer = renderer; +} + +WebGLManager.prototype.constructor = WebGLManager; +module.exports = WebGLManager; + +WebGLManager.prototype.destroy = function () { + this.renderer = null; +}; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js new file mode 100644 index 0000000..6d46d85 --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -0,0 +1,41 @@ +var WebGLManager = require('./WebGLManager'), + WebGLGraphics = require('./WebGLGraphics'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLMaskManager(renderer) { + WebGLManager.call(this, renderer); +} + +WebGLMaskManager.prototype = Object.create(WebGLManager.prototype); +WebGLMaskManager.prototype.constructor = WebGLMaskManager; +module.exports = WebGLMaskManager; + +/** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {any[]} + */ +WebGLMaskManager.prototype.pushMask = function (maskData) { + if (maskData.dirty) { + WebGLGraphics.updateGraphics(maskData, this.renderer.gl); + } + + if (!maskData._webGL[this.renderer.gl.id].data.length) { + return; + } + + this.renderer.stencilManager.pushStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); +}; + +/** + * Removes the last filter from the filter stack and doesn't return it. + * + * @param maskData {any[]} + */ +WebGLMaskManager.prototype.popMask = function (maskData) { + this.renderer.stencilManager.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 new file mode 100644 index 0000000..d02d95d --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -0,0 +1,172 @@ +var WebGLManager = require('./WebGLManager'), + PrimitiveShader = require('../shaders/PrimitiveShader'), + ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), + PixiShader = require('../shaders/PixiShader'), + PixiFastShader = require('../shaders/PixiFastShader'), + StripShader = require('../shaders/StripShader'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLShaderManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {number} + */ + this.maxAttibs = 10; + + /** + * @member {any[]} + */ + this.attribState = []; + + /** + * @member {any[]} + */ + this.tempAttribState = []; + + for (var i = 0; i < this.maxAttibs; i++) { + this.attribState[i] = false; + } + + /** + * @member {any[]} + */ + this.stack = []; + + /** + * @member {number} + * @private + */ + this._currentId = -1; + + /** + * @member {Shader} + * @private + */ + 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; + + // listen for context and update necessary shaders + var self = this; + this.renderer.on('context', function (gl) { + // 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 PixiShader(gl); + + // this shader is used for the fast sprite rendering + self.fastShader = new PixiFastShader(gl); + + // the next one is used for rendering triangle strips + self.stripShader = new StripShader(gl); + + self.setShader(self.defaultShader); + }); +} + +WebGLShaderManager.prototype = Object.create(WebGLManager.prototype); +WebGLShaderManager.prototype.constructor = WebGLShaderManager; +module.exports = WebGLShaderManager; + +/** + * Takes the attributes given in parameters. + * + * @param attribs {Array} attribs + */ +WebGLShaderManager.prototype.setAttribs = function (attribs) { + // reset temp state + var i; + + for (i = 0; i < this.tempAttribState.length; i++) { + this.tempAttribState[i] = false; + } + + // set the new attribs + for (var a in attribs) { + this.tempAttribState[attribs[a]] = true; + } + + var gl = this.renderer.gl; + + for (i = 0; i < this.attribState.length; i++) { + if (this.attribState[i] !== this.tempAttribState[i]) { + this.attribState[i] = this.tempAttribState[i]; + + if (this.attribState[i]) { + gl.enableVertexAttribArray(i); + } + else { + gl.disableVertexAttribArray(i); + } + } + } +}; + +/** + * Sets the current shader. + * + * @param shader {Any} + */ +WebGLShaderManager.prototype.setShader = function (shader) { + if (this._currentId === shader.uuid) { + return false; + } + + this._currentId = shader.uuid; + + this.currentShader = shader; + + this.renderer.gl.useProgram(shader.program); + this.setAttribs(shader.attributes); + + return true; +}; + +/** + * Destroys this object. + * + */ +WebGLShaderManager.prototype.destroy = function () { + 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/managers/WebGLStencilManager.js b/src/core/renderers/webgl/managers/WebGLStencilManager.js new file mode 100644 index 0000000..0bedae6 --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLStencilManager.js @@ -0,0 +1,256 @@ +var WebGLManager = require('./WebGLManager'), + utils = require('../../../utils'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLStencilManager(renderer) { + WebGLManager.call(this, renderer); + + this.stencilStack = []; + this.reverse = true; + this.count = 0; +} + +WebGLStencilManager.prototype = Object.create(WebGLManager.prototype); +WebGLStencilManager.prototype.constructor = WebGLStencilManager; +module.exports = WebGLStencilManager; + +/** + * Applies the Mask and adds it to the current filter stack. + * + * @param graphics {Graphics} + * @param webGLData {any[]} + */ +WebGLStencilManager.prototype.pushStencil = function (graphics, webGLData) { + var gl = this.renderer.gl; + + this.bindGraphics(graphics, webGLData, this.renderer); + + if (this.stencilStack.length === 0) { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + this.reverse = true; + this.count = 0; + } + + this.stencilStack.push(webGLData); + + var level = this.count; + + gl.colorMask(false, false, false, false); + + gl.stencilFunc(gl.ALWAYS,0,0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); + + // draw the triangle strip! + + if (webGLData.mode === 1) { + gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + + // draw a quad to increment.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + } + + this.reverse = !this.reverse; + } + else { + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + } + } + + gl.colorMask(true, true, true, true); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + + this.count++; +}; + +/** + * TODO this does not belong here! + * + * @param graphics {Graphics} + * @param webGLData {Array} + */ +WebGLStencilManager.prototype.bindGraphics = function (graphics, webGLData) { + //if (this._currentGraphics === graphics)return; + this._currentGraphics = graphics; + + var gl = this.renderer.gl; + + // bind the graphics object.. + var projection = this.renderer.projection, + offset = this.renderer.offset, + shader;// = this.renderer.shaderManager.primitiveShader; + + if (webGLData.mode === 1) { + shader = this.renderer.shaderManager.complexPrimitiveShader; + + this.renderer.shaderManager.setShader(shader); + + gl.uniform1f(shader.flipY, this.renderer.flipY); + + gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); + + gl.uniform2f(shader.projectionVector, projection.x, -projection.y); + gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); + + gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); + gl.uniform3fv(shader.color, webGLData.color); + + gl.uniform1f(shader.alpha, graphics.worldAlpha * webGLData.alpha); + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 2, 0); + + + // now do the rest.. + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + } + else { + //this.renderer.shaderManager.activatePrimitiveShader(); + shader = this.renderer.shaderManager.primitiveShader; + this.renderer.shaderManager.setShader( shader ); + + gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); + + gl.uniform1f(shader.flipY, this.renderer.flipY); + gl.uniform2f(shader.projectionVector, projection.x, -projection.y); + gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); + + gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); + + gl.uniform1f(shader.alpha, graphics.worldAlpha); + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); + gl.vertexAttribPointer(shader.colorAttribute, 4, gl.FLOAT, false,4 * 6, 2 * 4); + + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + } +}; + +/** + * @param graphics {Graphics} + * @param webGLData {Array} + */ +WebGLStencilManager.prototype.popStencil = function (graphics, webGLData) { + var gl = this.renderer.gl; + + this.stencilStack.pop(); + + this.count--; + + if (this.stencilStack.length === 0) { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + + } + else { + + var level = this.count; + + this.bindGraphics(graphics, webGLData, this.renderer); + + gl.colorMask(false, false, false, false); + + if (webGLData.mode === 1) { + this.reverse = !this.reverse; + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + + // draw a quad to increment.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + gl.stencilFunc(gl.ALWAYS,0,0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); + + // draw the triangle strip! + gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + } + + } + else { + // console.log("<<>>") + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + } + } + + gl.colorMask(true, true, true, true); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + + + } +}; + +/** + * Destroys the mask stack. + * + */ +WebGLStencilManager.prototype.destroy = function () { + this.renderer = null; + this.stencilStack = null; +}; diff --git a/src/core/renderers/webgl/utils/WebGLBlendModeManager.js b/src/core/renderers/webgl/utils/WebGLBlendModeManager.js deleted file mode 100644 index 9f2f89a..0000000 --- a/src/core/renderers/webgl/utils/WebGLBlendModeManager.js +++ /dev/null @@ -1,37 +0,0 @@ -var WebGLManager = require('./WebGLManager'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLBlendModeManager(renderer) { - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -WebGLBlendModeManager.prototype = Object.create(WebGLManager.prototype); -WebGLBlendModeManager.prototype.constructor = WebGLBlendModeManager; -module.exports = WebGLBlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as BlendModes.ADD - */ -WebGLBlendModeManager.prototype.setBlendMode = function (blendMode) { - if (this.currentBlendMode === blendMode) { - return false; - } - - this.currentBlendMode = blendMode; - - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); - - return true; -}; diff --git a/src/core/renderers/webgl/utils/WebGLFilterManager.js b/src/core/renderers/webgl/utils/WebGLFilterManager.js deleted file mode 100644 index 176e7cd..0000000 --- a/src/core/renderers/webgl/utils/WebGLFilterManager.js +++ /dev/null @@ -1,446 +0,0 @@ -var WebGLManager = require('./WebGLManager'), - FilterTexture = require('./FilterTexture'), - PixiShader = require('../shaders/PixiShader'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLFilterManager(renderer) { - WebGLManager.call(this, renderer); - - /** - * @member {any[]} - */ - this.filterStack = []; - - /** - * @member {any[]]} - */ - this.texturePool = []; - - /** - * @member {number} - */ - this.offsetX = 0; - - /** - * @member {number} - */ - this.offsetY = 0; - - // listen for context and update necessary buffers - var self = this; - this.renderer.on('context', function () { - self.texturePool.length = 0; - self.initShaderBuffers(); - }); -} - -WebGLFilterManager.prototype = Object.create(WebGLManager.prototype); -WebGLFilterManager.prototype.constructor = WebGLFilterManager; -module.exports = WebGLFilterManager; - -/** - * @param renderer {WebGLRenderer} - * @param buffer {ArrayBuffer} - */ -WebGLFilterManager.prototype.begin = function (renderer, buffer) { - this.renderer = renderer; - this.defaultShader = renderer.shaderManager.defaultShader; - - this.width = renderer.projection.x * 2; - this.height = -renderer.projection.y * 2; - - this.buffer = buffer; -}; - -/** - * Applies the filter and adds it to the current filter stack. - * - * @param filterBlock {object} the filter that will be pushed to the current filter stack - */ -WebGLFilterManager.prototype.pushFilter = function (filterBlock) { - var gl = this.renderer.gl; - - var projection = this.renderer.projection; - var offset = this.renderer.offset; - - filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds(); - - // filter program - // OPTIMISATION - the first filter is free if its a simple color change? - this.filterStack.push(filterBlock); - - var filter = filterBlock.filterPasses[0]; - - this.offsetX += filterBlock._filterArea.x; - this.offsetY += filterBlock._filterArea.y; - - var texture = this.texturePool.pop(); - if (!texture) { - texture = new FilterTexture(this.renderer.gl, this.width, this.height); - } - else { - texture.resize(this.width, this.height); - } - - gl.bindTexture(gl.TEXTURE_2D, texture.texture); - - var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea; - - var padding = filter.padding; - filterArea.x -= padding; - filterArea.y -= padding; - filterArea.width += padding * 2; - filterArea.height += padding * 2; - - // cap filter to screen size.. - if (filterArea.x < 0) { - filterArea.x = 0; - } - - if (filterArea.width > this.width) { - filterArea.width = this.width; - } - - if (filterArea.y < 0) { - filterArea.y = 0; - } - - if (filterArea.height > this.height) { - filterArea.height = this.height; - } - - //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); - - // set view port - gl.viewport(0, 0, filterArea.width, filterArea.height); - - projection.x = filterArea.width/2; - projection.y = -filterArea.height/2; - - offset.x = -filterArea.x; - offset.y = -filterArea.y; - - // update projection - // now restore the regular shader.. - // this.renderer.shaderManager.setShader(this.defaultShader); - //gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); - //gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y); - - gl.colorMask(true, true, true, true); - gl.clearColor(0,0,0, 0); - gl.clear(gl.COLOR_BUFFER_BIT); - - filterBlock._glFilterTexture = texture; - -}; - -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -WebGLFilterManager.prototype.popFilter = function () { - var gl = this.renderer.gl; - - var filterBlock = this.filterStack.pop(); - var filterArea = filterBlock._filterArea; - var texture = filterBlock._glFilterTexture; - var projection = this.renderer.projection; - var offset = this.renderer.offset; - - if (filterBlock.filterPasses.length > 1) { - gl.viewport(0, 0, filterArea.width, filterArea.height); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - - this.vertexArray[0] = 0; - this.vertexArray[1] = filterArea.height; - - this.vertexArray[2] = filterArea.width; - this.vertexArray[3] = filterArea.height; - - this.vertexArray[4] = 0; - this.vertexArray[5] = 0; - - this.vertexArray[6] = filterArea.width; - this.vertexArray[7] = 0; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - // now set the uvs.. - this.uvArray[2] = filterArea.width/this.width; - this.uvArray[5] = filterArea.height/this.height; - this.uvArray[6] = filterArea.width/this.width; - this.uvArray[7] = filterArea.height/this.height; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); - - var inputTexture = texture; - var outputTexture = this.texturePool.pop(); - if (!outputTexture) { - outputTexture = new FilterTexture(this.renderer.gl, this.width, this.height); - } - outputTexture.resize(this.width, this.height); - - // need to clear this FBO as it may have some left over elements from a previous filter. - gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); - gl.clear(gl.COLOR_BUFFER_BIT); - - gl.disable(gl.BLEND); - - for (var i = 0; i < filterBlock.filterPasses.length-1; i++) { - var filterPass = filterBlock.filterPasses[i]; - - gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); - - // set texture - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); - - // draw texture.. - //filterPass.applyFilterPass(filterArea.width, filterArea.height); - this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height); - - // swap the textures.. - var temp = inputTexture; - inputTexture = outputTexture; - outputTexture = temp; - } - - gl.enable(gl.BLEND); - - texture = inputTexture; - this.texturePool.push(outputTexture); - } - - var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1]; - - this.offsetX -= filterArea.x; - this.offsetY -= filterArea.y; - - var sizeX = this.width; - var sizeY = this.height; - - var offsetX = 0; - var offsetY = 0; - - var buffer = this.buffer; - - // time to render the filters texture to the previous scene - if (this.filterStack.length === 0) { - gl.colorMask(true, true, true, true);//this.transparent); - } - else { - var currentFilter = this.filterStack[this.filterStack.length-1]; - filterArea = currentFilter._filterArea; - - sizeX = filterArea.width; - sizeY = filterArea.height; - - offsetX = filterArea.x; - offsetY = filterArea.y; - - buffer = currentFilter._glFilterTexture.frameBuffer; - } - - // TODO need to remove these global elements.. - projection.x = sizeX/2; - projection.y = -sizeY/2; - - offset.x = offsetX; - offset.y = offsetY; - - filterArea = filterBlock._filterArea; - - var x = filterArea.x-offsetX; - var y = filterArea.y-offsetY; - - // update the buffers.. - // make sure to flip the y! - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - - this.vertexArray[0] = x; - this.vertexArray[1] = y + filterArea.height; - - this.vertexArray[2] = x + filterArea.width; - this.vertexArray[3] = y + filterArea.height; - - this.vertexArray[4] = x; - this.vertexArray[5] = y; - - this.vertexArray[6] = x + filterArea.width; - this.vertexArray[7] = y; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - - this.uvArray[2] = filterArea.width/this.width; - this.uvArray[5] = filterArea.height/this.height; - this.uvArray[6] = filterArea.width/this.width; - this.uvArray[7] = filterArea.height/this.height; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); - - gl.viewport(0, 0, sizeX, sizeY); - - // bind the buffer - gl.bindFramebuffer(gl.FRAMEBUFFER, buffer ); - - // set the blend mode! - //gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) - - // set texture - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, texture.texture); - - // apply! - this.applyFilterPass(filter, filterArea, sizeX, sizeY); - - // now restore the regular shader.. should happen automatically now.. - // this.renderer.shaderManager.setShader(this.defaultShader); - // gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2); - // gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY); - - // return the texture to the pool - this.texturePool.push(texture); - filterBlock._glFilterTexture = null; -}; - - -/** - * Applies the filter to the specified area. - * - * @param filter {AbstractFilter} the filter that needs to be applied - * @param filterArea {Texture} TODO - might need an update - * @param width {number} the horizontal range of the filter - * @param height {number} the vertical range of the filter - */ -WebGLFilterManager.prototype.applyFilterPass = function (filter, filterArea, width, height) { - // use program - var gl = this.renderer.gl; - - var shader = filter.shaders[gl.id]; - - if (!shader) { - shader = new PixiShader(gl); - - shader.fragmentSrc = filter.fragmentSrc; - shader.uniforms = filter.uniforms; - shader.init(); - - filter.shaders[gl.id] = shader; - } - - // set the shader - this.renderer.shaderManager.setShader(shader); - -// gl.useProgram(shader.program); - - gl.uniform2f(shader.projectionVector, width/2, -height/2); - gl.uniform2f(shader.offsetVector, 0,0); - - if (filter.uniforms.dimensions) { - filter.uniforms.dimensions.value[0] = this.width;//width; - filter.uniforms.dimensions.value[1] = this.height;//height; - filter.uniforms.dimensions.value[2] = this.vertexArray[0]; - filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; - } - - shader.syncUniforms(); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); - gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - - // draw the filter... - gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); - - this.renderer.drawCount++; -}; - -/** - * Initialises the shader buffers. - * - */ -WebGLFilterManager.prototype.initShaderBuffers = function () { - var gl = this.renderer.gl; - - // create some buffers - this.vertexBuffer = gl.createBuffer(); - this.uvBuffer = gl.createBuffer(); - this.colorBuffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - - // bind and upload the vertexs.. - // keep a reference to the vertexFloatData.. - this.vertexArray = new Float32Array([0.0, 0.0, - 1.0, 0.0, - 0.0, 1.0, - 1.0, 1.0]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW); - - // bind and upload the uv buffer - this.uvArray = new Float32Array([0.0, 0.0, - 1.0, 0.0, - 0.0, 1.0, - 1.0, 1.0]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.uvArray, gl.STATIC_DRAW); - - this.colorArray = new Float32Array([1.0, 0xFFFFFF, - 1.0, 0xFFFFFF, - 1.0, 0xFFFFFF, - 1.0, 0xFFFFFF]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.colorArray, gl.STATIC_DRAW); - - // bind and upload the index - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW); - -}; - -/** - * Destroys the filter and removes it from the filter stack. - * - */ -WebGLFilterManager.prototype.destroy = function () { - var gl = this.renderer.gl; - - this.filterStack = null; - - this.offsetX = 0; - this.offsetY = 0; - - // destroy textures - for (var i = 0; i < this.texturePool.length; i++) { - this.texturePool[i].destroy(); - } - - this.texturePool = null; - - //destroy buffers.. - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.uvBuffer); - gl.deleteBuffer(this.colorBuffer); - gl.deleteBuffer(this.indexBuffer); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/WebGLGraphics.js b/src/core/renderers/webgl/utils/WebGLGraphics.js index 09f540f..1d1fe40 100644 --- a/src/core/renderers/webgl/utils/WebGLGraphics.js +++ b/src/core/renderers/webgl/utils/WebGLGraphics.js @@ -1,6 +1,5 @@ var utils = require('../../../utils'), math = require('../../../math'), - Graphics = require('../../../../primitives/Graphics'), WebGLGraphicsData = require('./WebGLGraphicsData'); /** @@ -122,7 +121,7 @@ for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) { var data = graphics.graphicsData[i]; - if (data.type === Graphics.POLY) { + 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) { @@ -164,13 +163,13 @@ else { webGLData = WebGLGraphics.switchMode(webGL, 0); - if (data.type === Graphics.RECT) { + if (data.type === CONST.SHAPES.RECT) { WebGLGraphics.buildRectangle(data, webGLData); } - else if (data.type === Graphics.CIRC || data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) { WebGLGraphics.buildCircle(data, webGLData); } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { WebGLGraphics.buildRoundedRectangle(data, webGLData); } } @@ -415,7 +414,7 @@ var height; // TODO - bit hacky?? - if (graphicsData.type === Graphics.CIRC) { + if (graphicsData.type === CONST.SHAPES.CIRC) { width = circleData.radius; height = circleData.radius; } diff --git a/src/core/renderers/webgl/utils/WebGLManager.js b/src/core/renderers/webgl/utils/WebGLManager.js deleted file mode 100644 index 9d47b5c..0000000 --- a/src/core/renderers/webgl/utils/WebGLManager.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLManager(renderer) { - /** - * The renderer this manager works for. - * - * @member {WebGLRenderer} - */ - this.renderer = renderer; -} - -WebGLManager.prototype.constructor = WebGLManager; -module.exports = WebGLManager; - -WebGLManager.prototype.destroy = function () { - this.renderer = null; -}; diff --git a/src/core/const.js b/src/core/const.js index 2803e5b..aea72ec 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -130,5 +130,25 @@ backgroundColor: 0x000000, clearBeforeRender: true, preserveDrawingBuffer: false + }, + + /** + * Constants that identify shapes, mainly to prevent `instanceof` calls. + * + * @static + * @constant + * @property {object} SHAPES + * @property {object} SHAPES.POLY=0 + * @property {object} SHAPES.RECT=1 + * @property {object} SHAPES.CIRC=2 + * @property {object} SHAPES.ELIP=3 + * @property {object} SHAPES.RREC=4 + */ + SHAPES: { + POLY = 0; + RECT = 1; + CIRC = 2; + ELIP = 3; + RREC = 4; } }; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index bfd59cf..7c0e7c6 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -95,7 +95,7 @@ this.filterArea = null; /** - * cached sin rotation and cos rotation + * cached sin rotation * * @member {number} * @private @@ -103,7 +103,7 @@ this._sr = 0; /** - * cached sin rotation and cos rotation + * cached cos rotation * * @member {number} * @private @@ -400,7 +400,7 @@ DisplayObject.prototype.generateTexture = function (resolution, scaleMode, renderer) { var bounds = this.getLocalBounds(); - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); _tempMatrix.tx = -bounds.x; _tempMatrix.ty = -bounds.y; @@ -474,7 +474,7 @@ var bounds = this.getLocalBounds(); if (!this._cachedSprite) { - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0); //, renderer); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0); this._cachedSprite = new Sprite(renderTexture); this._cachedSprite.worldTransform = this.worldTransform; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 3f6a3b6..69cfdc9 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Circle object can be used to specify a hit area for displayObjects @@ -29,10 +30,11 @@ this.radius = radius || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.CIRC in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.CIRC; } Circle.prototype.constructor = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index fc9df4d..4f2d066 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Ellipse object can be used to specify a hit area for displayObjects @@ -36,10 +37,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.ELIP in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.ELIP; } Ellipse.prototype.constructor = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index 5d4a464..ae89148 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -1,4 +1,5 @@ -var Point = require('../Point'); +var Point = require('../Point'), + CONST = require('../../const'); /** * @class @@ -33,6 +34,13 @@ * @member {Point[]} */ this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + */ + this.type = CONST.SHAPES.POLY; } Polygon.prototype.constructor = Polygon; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index c3454b5..16ac62a 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * the Rectangle object is an area defined by its position, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -34,10 +36,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RECT; } Rectangle.prototype.constructor = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0f2abfc..27d286e 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * The Rounded Rectangle object is an area defined by its position and has nice rounded corners, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -41,10 +43,11 @@ this.radius = radius || 20; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RRECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RREC; } RoundedRectangle.prototype.constructor = RoundedRectangle; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js new file mode 100644 index 0000000..42647c5 --- /dev/null +++ b/src/core/primitives/Graphics.js @@ -0,0 +1,1029 @@ +var core = require('../core'), + GraphicsData = require('./GraphicsData'); + +/** + * 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() { + core.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 {object[]} + * @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 = core.CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {object} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object[]} + * @private + */ + 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 core.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(core.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; + } + } + } +}); + +/** + * 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 core.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 core.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; + + 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 core.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 core.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 core.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 core.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 core.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 core.CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = core.Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + core.CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @method _renderWebGL + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderWebGL = function (renderSession) { + // 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 on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + core.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); + + return; + } + else { + renderSession.spriteBatch.stop(); + renderSession.blendModeManager.setBlendMode(this.blendMode); + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + if (this._filters) { + renderSession.filterManager.pushFilter(this._filterBlock); + } + + // check blend mode + if (this.blendMode !== renderSession.spriteBatch.currentBlendMode) { + renderSession.spriteBatch.currentBlendMode = this.blendMode; + + var blendModeWebGL = blendModesWebGL[renderSession.spriteBatch.currentBlendMode]; + + renderSession.spriteBatch.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); + } + + // check if the webgl graphic needs to be updated + if (this.glDirty) { + this.dirty = true; + this.glDirty = false; + } + + core.WebGLGraphics.renderGraphics(this, renderSession); + + // only render if it has children! + if (this.children.length) { + renderSession.spriteBatch.start(); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderWebGL(renderSession); + } + + renderSession.spriteBatch.stop(); + } + + if (this._filters) { + renderSession.filterManager.popFilter(); + } + + if (this._mask) { + renderSession.maskManager.popMask(this.mask, renderSession); + } + + renderSession.drawCount++; + + renderSession.spriteBatch.start(); + } +}; + +/** + * Renders the object using the Canvas renderer + * + * @method _renderCanvas + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderCanvas = function (renderSession) { + // 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; + + core.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession); + + return; + } + else { + var context = renderSession.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderSession.currentBlendMode) { + renderSession.currentBlendMode = this.blendMode; + context.globalCompositeOperation = blendModesCanvas[renderSession.currentBlendMode]; + } + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + var resolution = renderSession.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + core.CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderCanvas(renderSession); + } + + if (this._mask) { + renderSession.maskManager.popMask(renderSession); + } + } +}; + +/** + * 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 core.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 === core.CONST.SHAPES.RECT || type === core.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 === core.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 === core.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 core.CanvasBuffer(bounds.width, bounds.height); + var texture = core.Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new core.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.. + core.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 === core.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 new file mode 100644 index 0000000..9377ef7 --- /dev/null +++ b/src/core/primitives/GraphicsData.js @@ -0,0 +1,23 @@ +/** + * 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; diff --git a/src/core/primitives/index.js b/src/core/primitives/index.js new file mode 100644 index 0000000..7f02649 --- /dev/null +++ b/src/core/primitives/index.js @@ -0,0 +1,14 @@ +/** + * @file Main export of the PIXI primitives library + * @author Mat Groves + * @copyright 2013-2015 GoodBoyDigital + * @license {@link https://github.com/GoodBoyDigital/pixi.js/blob/master/LICENSE|MIT License} + */ + +/** + * @namespace PIXI + */ +module.exports = { + Graphics: require('./Graphics'), + GraphicsData: require('./GraphicsData') +}; diff --git a/src/core/renderers/canvas/CanvasGraphics.js b/src/core/renderers/canvas/CanvasGraphics.js index 2b1797a..ef7e765 100644 --- a/src/core/renderers/canvas/CanvasGraphics.js +++ b/src/core/renderers/canvas/CanvasGraphics.js @@ -1,4 +1,4 @@ -var Graphics = require('../../../primitives/Graphics'); +var CONST = require('../../const'); /** * A set of functions used by the canvas renderer to draw the primitive graphics data. @@ -30,7 +30,7 @@ context.lineWidth = data.lineWidth; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -61,7 +61,7 @@ context.stroke(); } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { if (data.fillColor || data.fillColor === 0) { context.globalAlpha = data.fillAlpha * worldAlpha; @@ -75,7 +75,7 @@ context.strokeRect(shape.x, shape.y, shape.width, shape.height); } } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); @@ -92,7 +92,7 @@ context.stroke(); } } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas var w = shape.width * 2; @@ -130,7 +130,7 @@ context.stroke(); } } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var rx = shape.x; var ry = shape.y; var width = shape.width; @@ -190,7 +190,7 @@ var data = graphics.graphicsData[i]; var shape = data.shape; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -207,18 +207,18 @@ } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { context.beginPath(); context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); context.closePath(); } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas @@ -245,7 +245,7 @@ context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); context.closePath(); } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var pts = shape.points; var rx = pts[0]; diff --git a/src/core/renderers/webgl/managers/WebGLBlendModeManager.js b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js new file mode 100644 index 0000000..9f2f89a --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js @@ -0,0 +1,37 @@ +var WebGLManager = require('./WebGLManager'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLBlendModeManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; +} + +WebGLBlendModeManager.prototype = Object.create(WebGLManager.prototype); +WebGLBlendModeManager.prototype.constructor = WebGLBlendModeManager; +module.exports = WebGLBlendModeManager; + +/** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as BlendModes.ADD + */ +WebGLBlendModeManager.prototype.setBlendMode = function (blendMode) { + if (this.currentBlendMode === blendMode) { + return false; + } + + this.currentBlendMode = blendMode; + + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; +}; diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js new file mode 100644 index 0000000..176e7cd --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -0,0 +1,446 @@ +var WebGLManager = require('./WebGLManager'), + FilterTexture = require('./FilterTexture'), + PixiShader = require('../shaders/PixiShader'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLFilterManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {any[]} + */ + this.filterStack = []; + + /** + * @member {any[]]} + */ + this.texturePool = []; + + /** + * @member {number} + */ + this.offsetX = 0; + + /** + * @member {number} + */ + this.offsetY = 0; + + // listen for context and update necessary buffers + var self = this; + this.renderer.on('context', function () { + self.texturePool.length = 0; + self.initShaderBuffers(); + }); +} + +WebGLFilterManager.prototype = Object.create(WebGLManager.prototype); +WebGLFilterManager.prototype.constructor = WebGLFilterManager; +module.exports = WebGLFilterManager; + +/** + * @param renderer {WebGLRenderer} + * @param buffer {ArrayBuffer} + */ +WebGLFilterManager.prototype.begin = function (renderer, buffer) { + this.renderer = renderer; + this.defaultShader = renderer.shaderManager.defaultShader; + + this.width = renderer.projection.x * 2; + this.height = -renderer.projection.y * 2; + + this.buffer = buffer; +}; + +/** + * Applies the filter and adds it to the current filter stack. + * + * @param filterBlock {object} the filter that will be pushed to the current filter stack + */ +WebGLFilterManager.prototype.pushFilter = function (filterBlock) { + var gl = this.renderer.gl; + + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds(); + + // filter program + // OPTIMISATION - the first filter is free if its a simple color change? + this.filterStack.push(filterBlock); + + var filter = filterBlock.filterPasses[0]; + + this.offsetX += filterBlock._filterArea.x; + this.offsetY += filterBlock._filterArea.y; + + var texture = this.texturePool.pop(); + if (!texture) { + texture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + else { + texture.resize(this.width, this.height); + } + + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea; + + var padding = filter.padding; + filterArea.x -= padding; + filterArea.y -= padding; + filterArea.width += padding * 2; + filterArea.height += padding * 2; + + // cap filter to screen size.. + if (filterArea.x < 0) { + filterArea.x = 0; + } + + if (filterArea.width > this.width) { + filterArea.width = this.width; + } + + if (filterArea.y < 0) { + filterArea.y = 0; + } + + if (filterArea.height > this.height) { + filterArea.height = this.height; + } + + //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); + + // set view port + gl.viewport(0, 0, filterArea.width, filterArea.height); + + projection.x = filterArea.width/2; + projection.y = -filterArea.height/2; + + offset.x = -filterArea.x; + offset.y = -filterArea.y; + + // update projection + // now restore the regular shader.. + // this.renderer.shaderManager.setShader(this.defaultShader); + //gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); + //gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y); + + gl.colorMask(true, true, true, true); + gl.clearColor(0,0,0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + + filterBlock._glFilterTexture = texture; + +}; + +/** + * Removes the last filter from the filter stack and doesn't return it. + * + */ +WebGLFilterManager.prototype.popFilter = function () { + var gl = this.renderer.gl; + + var filterBlock = this.filterStack.pop(); + var filterArea = filterBlock._filterArea; + var texture = filterBlock._glFilterTexture; + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + if (filterBlock.filterPasses.length > 1) { + gl.viewport(0, 0, filterArea.width, filterArea.height); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = 0; + this.vertexArray[1] = filterArea.height; + + this.vertexArray[2] = filterArea.width; + this.vertexArray[3] = filterArea.height; + + this.vertexArray[4] = 0; + this.vertexArray[5] = 0; + + this.vertexArray[6] = filterArea.width; + this.vertexArray[7] = 0; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + // now set the uvs.. + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + var inputTexture = texture; + var outputTexture = this.texturePool.pop(); + if (!outputTexture) { + outputTexture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + outputTexture.resize(this.width, this.height); + + // need to clear this FBO as it may have some left over elements from a previous filter. + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + gl.clear(gl.COLOR_BUFFER_BIT); + + gl.disable(gl.BLEND); + + for (var i = 0; i < filterBlock.filterPasses.length-1; i++) { + var filterPass = filterBlock.filterPasses[i]; + + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); + + // draw texture.. + //filterPass.applyFilterPass(filterArea.width, filterArea.height); + this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height); + + // swap the textures.. + var temp = inputTexture; + inputTexture = outputTexture; + outputTexture = temp; + } + + gl.enable(gl.BLEND); + + texture = inputTexture; + this.texturePool.push(outputTexture); + } + + var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1]; + + this.offsetX -= filterArea.x; + this.offsetY -= filterArea.y; + + var sizeX = this.width; + var sizeY = this.height; + + var offsetX = 0; + var offsetY = 0; + + var buffer = this.buffer; + + // time to render the filters texture to the previous scene + if (this.filterStack.length === 0) { + gl.colorMask(true, true, true, true);//this.transparent); + } + else { + var currentFilter = this.filterStack[this.filterStack.length-1]; + filterArea = currentFilter._filterArea; + + sizeX = filterArea.width; + sizeY = filterArea.height; + + offsetX = filterArea.x; + offsetY = filterArea.y; + + buffer = currentFilter._glFilterTexture.frameBuffer; + } + + // TODO need to remove these global elements.. + projection.x = sizeX/2; + projection.y = -sizeY/2; + + offset.x = offsetX; + offset.y = offsetY; + + filterArea = filterBlock._filterArea; + + var x = filterArea.x-offsetX; + var y = filterArea.y-offsetY; + + // update the buffers.. + // make sure to flip the y! + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = x; + this.vertexArray[1] = y + filterArea.height; + + this.vertexArray[2] = x + filterArea.width; + this.vertexArray[3] = y + filterArea.height; + + this.vertexArray[4] = x; + this.vertexArray[5] = y; + + this.vertexArray[6] = x + filterArea.width; + this.vertexArray[7] = y; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + gl.viewport(0, 0, sizeX, sizeY); + + // bind the buffer + gl.bindFramebuffer(gl.FRAMEBUFFER, buffer ); + + // set the blend mode! + //gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + // apply! + this.applyFilterPass(filter, filterArea, sizeX, sizeY); + + // now restore the regular shader.. should happen automatically now.. + // this.renderer.shaderManager.setShader(this.defaultShader); + // gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2); + // gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY); + + // return the texture to the pool + this.texturePool.push(texture); + filterBlock._glFilterTexture = null; +}; + + +/** + * Applies the filter to the specified area. + * + * @param filter {AbstractFilter} the filter that needs to be applied + * @param filterArea {Texture} TODO - might need an update + * @param width {number} the horizontal range of the filter + * @param height {number} the vertical range of the filter + */ +WebGLFilterManager.prototype.applyFilterPass = function (filter, filterArea, width, height) { + // use program + var gl = this.renderer.gl; + + var shader = filter.shaders[gl.id]; + + if (!shader) { + shader = new PixiShader(gl); + + shader.fragmentSrc = filter.fragmentSrc; + shader.uniforms = filter.uniforms; + shader.init(); + + filter.shaders[gl.id] = shader; + } + + // set the shader + this.renderer.shaderManager.setShader(shader); + +// gl.useProgram(shader.program); + + gl.uniform2f(shader.projectionVector, width/2, -height/2); + gl.uniform2f(shader.offsetVector, 0,0); + + if (filter.uniforms.dimensions) { + filter.uniforms.dimensions.value[0] = this.width;//width; + filter.uniforms.dimensions.value[1] = this.height;//height; + filter.uniforms.dimensions.value[2] = this.vertexArray[0]; + filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; + } + + shader.syncUniforms(); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + + // draw the filter... + gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); + + this.renderer.drawCount++; +}; + +/** + * Initialises the shader buffers. + * + */ +WebGLFilterManager.prototype.initShaderBuffers = function () { + var gl = this.renderer.gl; + + // create some buffers + this.vertexBuffer = gl.createBuffer(); + this.uvBuffer = gl.createBuffer(); + this.colorBuffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + + // bind and upload the vertexs.. + // keep a reference to the vertexFloatData.. + this.vertexArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW); + + // bind and upload the uv buffer + this.uvArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.uvArray, gl.STATIC_DRAW); + + this.colorArray = new Float32Array([1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.colorArray, gl.STATIC_DRAW); + + // bind and upload the index + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW); + +}; + +/** + * Destroys the filter and removes it from the filter stack. + * + */ +WebGLFilterManager.prototype.destroy = function () { + var gl = this.renderer.gl; + + this.filterStack = null; + + this.offsetX = 0; + this.offsetY = 0; + + // destroy textures + for (var i = 0; i < this.texturePool.length; i++) { + this.texturePool[i].destroy(); + } + + this.texturePool = null; + + //destroy buffers.. + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.uvBuffer); + gl.deleteBuffer(this.colorBuffer); + gl.deleteBuffer(this.indexBuffer); + + this.renderer = null; +}; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js new file mode 100644 index 0000000..9d47b5c --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -0,0 +1,20 @@ +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLManager(renderer) { + /** + * The renderer this manager works for. + * + * @member {WebGLRenderer} + */ + this.renderer = renderer; +} + +WebGLManager.prototype.constructor = WebGLManager; +module.exports = WebGLManager; + +WebGLManager.prototype.destroy = function () { + this.renderer = null; +}; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js new file mode 100644 index 0000000..6d46d85 --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -0,0 +1,41 @@ +var WebGLManager = require('./WebGLManager'), + WebGLGraphics = require('./WebGLGraphics'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLMaskManager(renderer) { + WebGLManager.call(this, renderer); +} + +WebGLMaskManager.prototype = Object.create(WebGLManager.prototype); +WebGLMaskManager.prototype.constructor = WebGLMaskManager; +module.exports = WebGLMaskManager; + +/** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {any[]} + */ +WebGLMaskManager.prototype.pushMask = function (maskData) { + if (maskData.dirty) { + WebGLGraphics.updateGraphics(maskData, this.renderer.gl); + } + + if (!maskData._webGL[this.renderer.gl.id].data.length) { + return; + } + + this.renderer.stencilManager.pushStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); +}; + +/** + * Removes the last filter from the filter stack and doesn't return it. + * + * @param maskData {any[]} + */ +WebGLMaskManager.prototype.popMask = function (maskData) { + this.renderer.stencilManager.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 new file mode 100644 index 0000000..d02d95d --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -0,0 +1,172 @@ +var WebGLManager = require('./WebGLManager'), + PrimitiveShader = require('../shaders/PrimitiveShader'), + ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), + PixiShader = require('../shaders/PixiShader'), + PixiFastShader = require('../shaders/PixiFastShader'), + StripShader = require('../shaders/StripShader'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLShaderManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {number} + */ + this.maxAttibs = 10; + + /** + * @member {any[]} + */ + this.attribState = []; + + /** + * @member {any[]} + */ + this.tempAttribState = []; + + for (var i = 0; i < this.maxAttibs; i++) { + this.attribState[i] = false; + } + + /** + * @member {any[]} + */ + this.stack = []; + + /** + * @member {number} + * @private + */ + this._currentId = -1; + + /** + * @member {Shader} + * @private + */ + 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; + + // listen for context and update necessary shaders + var self = this; + this.renderer.on('context', function (gl) { + // 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 PixiShader(gl); + + // this shader is used for the fast sprite rendering + self.fastShader = new PixiFastShader(gl); + + // the next one is used for rendering triangle strips + self.stripShader = new StripShader(gl); + + self.setShader(self.defaultShader); + }); +} + +WebGLShaderManager.prototype = Object.create(WebGLManager.prototype); +WebGLShaderManager.prototype.constructor = WebGLShaderManager; +module.exports = WebGLShaderManager; + +/** + * Takes the attributes given in parameters. + * + * @param attribs {Array} attribs + */ +WebGLShaderManager.prototype.setAttribs = function (attribs) { + // reset temp state + var i; + + for (i = 0; i < this.tempAttribState.length; i++) { + this.tempAttribState[i] = false; + } + + // set the new attribs + for (var a in attribs) { + this.tempAttribState[attribs[a]] = true; + } + + var gl = this.renderer.gl; + + for (i = 0; i < this.attribState.length; i++) { + if (this.attribState[i] !== this.tempAttribState[i]) { + this.attribState[i] = this.tempAttribState[i]; + + if (this.attribState[i]) { + gl.enableVertexAttribArray(i); + } + else { + gl.disableVertexAttribArray(i); + } + } + } +}; + +/** + * Sets the current shader. + * + * @param shader {Any} + */ +WebGLShaderManager.prototype.setShader = function (shader) { + if (this._currentId === shader.uuid) { + return false; + } + + this._currentId = shader.uuid; + + this.currentShader = shader; + + this.renderer.gl.useProgram(shader.program); + this.setAttribs(shader.attributes); + + return true; +}; + +/** + * Destroys this object. + * + */ +WebGLShaderManager.prototype.destroy = function () { + 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/managers/WebGLStencilManager.js b/src/core/renderers/webgl/managers/WebGLStencilManager.js new file mode 100644 index 0000000..0bedae6 --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLStencilManager.js @@ -0,0 +1,256 @@ +var WebGLManager = require('./WebGLManager'), + utils = require('../../../utils'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLStencilManager(renderer) { + WebGLManager.call(this, renderer); + + this.stencilStack = []; + this.reverse = true; + this.count = 0; +} + +WebGLStencilManager.prototype = Object.create(WebGLManager.prototype); +WebGLStencilManager.prototype.constructor = WebGLStencilManager; +module.exports = WebGLStencilManager; + +/** + * Applies the Mask and adds it to the current filter stack. + * + * @param graphics {Graphics} + * @param webGLData {any[]} + */ +WebGLStencilManager.prototype.pushStencil = function (graphics, webGLData) { + var gl = this.renderer.gl; + + this.bindGraphics(graphics, webGLData, this.renderer); + + if (this.stencilStack.length === 0) { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + this.reverse = true; + this.count = 0; + } + + this.stencilStack.push(webGLData); + + var level = this.count; + + gl.colorMask(false, false, false, false); + + gl.stencilFunc(gl.ALWAYS,0,0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); + + // draw the triangle strip! + + if (webGLData.mode === 1) { + gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + + // draw a quad to increment.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + } + + this.reverse = !this.reverse; + } + else { + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + } + } + + gl.colorMask(true, true, true, true); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + + this.count++; +}; + +/** + * TODO this does not belong here! + * + * @param graphics {Graphics} + * @param webGLData {Array} + */ +WebGLStencilManager.prototype.bindGraphics = function (graphics, webGLData) { + //if (this._currentGraphics === graphics)return; + this._currentGraphics = graphics; + + var gl = this.renderer.gl; + + // bind the graphics object.. + var projection = this.renderer.projection, + offset = this.renderer.offset, + shader;// = this.renderer.shaderManager.primitiveShader; + + if (webGLData.mode === 1) { + shader = this.renderer.shaderManager.complexPrimitiveShader; + + this.renderer.shaderManager.setShader(shader); + + gl.uniform1f(shader.flipY, this.renderer.flipY); + + gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); + + gl.uniform2f(shader.projectionVector, projection.x, -projection.y); + gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); + + gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); + gl.uniform3fv(shader.color, webGLData.color); + + gl.uniform1f(shader.alpha, graphics.worldAlpha * webGLData.alpha); + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 2, 0); + + + // now do the rest.. + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + } + else { + //this.renderer.shaderManager.activatePrimitiveShader(); + shader = this.renderer.shaderManager.primitiveShader; + this.renderer.shaderManager.setShader( shader ); + + gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); + + gl.uniform1f(shader.flipY, this.renderer.flipY); + gl.uniform2f(shader.projectionVector, projection.x, -projection.y); + gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); + + gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); + + gl.uniform1f(shader.alpha, graphics.worldAlpha); + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); + gl.vertexAttribPointer(shader.colorAttribute, 4, gl.FLOAT, false,4 * 6, 2 * 4); + + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + } +}; + +/** + * @param graphics {Graphics} + * @param webGLData {Array} + */ +WebGLStencilManager.prototype.popStencil = function (graphics, webGLData) { + var gl = this.renderer.gl; + + this.stencilStack.pop(); + + this.count--; + + if (this.stencilStack.length === 0) { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + + } + else { + + var level = this.count; + + this.bindGraphics(graphics, webGLData, this.renderer); + + gl.colorMask(false, false, false, false); + + if (webGLData.mode === 1) { + this.reverse = !this.reverse; + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + + // draw a quad to increment.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + gl.stencilFunc(gl.ALWAYS,0,0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); + + // draw the triangle strip! + gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + } + + } + else { + // console.log("<<>>") + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + } + } + + gl.colorMask(true, true, true, true); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + + + } +}; + +/** + * Destroys the mask stack. + * + */ +WebGLStencilManager.prototype.destroy = function () { + this.renderer = null; + this.stencilStack = null; +}; diff --git a/src/core/renderers/webgl/utils/WebGLBlendModeManager.js b/src/core/renderers/webgl/utils/WebGLBlendModeManager.js deleted file mode 100644 index 9f2f89a..0000000 --- a/src/core/renderers/webgl/utils/WebGLBlendModeManager.js +++ /dev/null @@ -1,37 +0,0 @@ -var WebGLManager = require('./WebGLManager'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLBlendModeManager(renderer) { - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -WebGLBlendModeManager.prototype = Object.create(WebGLManager.prototype); -WebGLBlendModeManager.prototype.constructor = WebGLBlendModeManager; -module.exports = WebGLBlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as BlendModes.ADD - */ -WebGLBlendModeManager.prototype.setBlendMode = function (blendMode) { - if (this.currentBlendMode === blendMode) { - return false; - } - - this.currentBlendMode = blendMode; - - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); - - return true; -}; diff --git a/src/core/renderers/webgl/utils/WebGLFilterManager.js b/src/core/renderers/webgl/utils/WebGLFilterManager.js deleted file mode 100644 index 176e7cd..0000000 --- a/src/core/renderers/webgl/utils/WebGLFilterManager.js +++ /dev/null @@ -1,446 +0,0 @@ -var WebGLManager = require('./WebGLManager'), - FilterTexture = require('./FilterTexture'), - PixiShader = require('../shaders/PixiShader'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLFilterManager(renderer) { - WebGLManager.call(this, renderer); - - /** - * @member {any[]} - */ - this.filterStack = []; - - /** - * @member {any[]]} - */ - this.texturePool = []; - - /** - * @member {number} - */ - this.offsetX = 0; - - /** - * @member {number} - */ - this.offsetY = 0; - - // listen for context and update necessary buffers - var self = this; - this.renderer.on('context', function () { - self.texturePool.length = 0; - self.initShaderBuffers(); - }); -} - -WebGLFilterManager.prototype = Object.create(WebGLManager.prototype); -WebGLFilterManager.prototype.constructor = WebGLFilterManager; -module.exports = WebGLFilterManager; - -/** - * @param renderer {WebGLRenderer} - * @param buffer {ArrayBuffer} - */ -WebGLFilterManager.prototype.begin = function (renderer, buffer) { - this.renderer = renderer; - this.defaultShader = renderer.shaderManager.defaultShader; - - this.width = renderer.projection.x * 2; - this.height = -renderer.projection.y * 2; - - this.buffer = buffer; -}; - -/** - * Applies the filter and adds it to the current filter stack. - * - * @param filterBlock {object} the filter that will be pushed to the current filter stack - */ -WebGLFilterManager.prototype.pushFilter = function (filterBlock) { - var gl = this.renderer.gl; - - var projection = this.renderer.projection; - var offset = this.renderer.offset; - - filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds(); - - // filter program - // OPTIMISATION - the first filter is free if its a simple color change? - this.filterStack.push(filterBlock); - - var filter = filterBlock.filterPasses[0]; - - this.offsetX += filterBlock._filterArea.x; - this.offsetY += filterBlock._filterArea.y; - - var texture = this.texturePool.pop(); - if (!texture) { - texture = new FilterTexture(this.renderer.gl, this.width, this.height); - } - else { - texture.resize(this.width, this.height); - } - - gl.bindTexture(gl.TEXTURE_2D, texture.texture); - - var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea; - - var padding = filter.padding; - filterArea.x -= padding; - filterArea.y -= padding; - filterArea.width += padding * 2; - filterArea.height += padding * 2; - - // cap filter to screen size.. - if (filterArea.x < 0) { - filterArea.x = 0; - } - - if (filterArea.width > this.width) { - filterArea.width = this.width; - } - - if (filterArea.y < 0) { - filterArea.y = 0; - } - - if (filterArea.height > this.height) { - filterArea.height = this.height; - } - - //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); - - // set view port - gl.viewport(0, 0, filterArea.width, filterArea.height); - - projection.x = filterArea.width/2; - projection.y = -filterArea.height/2; - - offset.x = -filterArea.x; - offset.y = -filterArea.y; - - // update projection - // now restore the regular shader.. - // this.renderer.shaderManager.setShader(this.defaultShader); - //gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); - //gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y); - - gl.colorMask(true, true, true, true); - gl.clearColor(0,0,0, 0); - gl.clear(gl.COLOR_BUFFER_BIT); - - filterBlock._glFilterTexture = texture; - -}; - -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -WebGLFilterManager.prototype.popFilter = function () { - var gl = this.renderer.gl; - - var filterBlock = this.filterStack.pop(); - var filterArea = filterBlock._filterArea; - var texture = filterBlock._glFilterTexture; - var projection = this.renderer.projection; - var offset = this.renderer.offset; - - if (filterBlock.filterPasses.length > 1) { - gl.viewport(0, 0, filterArea.width, filterArea.height); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - - this.vertexArray[0] = 0; - this.vertexArray[1] = filterArea.height; - - this.vertexArray[2] = filterArea.width; - this.vertexArray[3] = filterArea.height; - - this.vertexArray[4] = 0; - this.vertexArray[5] = 0; - - this.vertexArray[6] = filterArea.width; - this.vertexArray[7] = 0; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - // now set the uvs.. - this.uvArray[2] = filterArea.width/this.width; - this.uvArray[5] = filterArea.height/this.height; - this.uvArray[6] = filterArea.width/this.width; - this.uvArray[7] = filterArea.height/this.height; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); - - var inputTexture = texture; - var outputTexture = this.texturePool.pop(); - if (!outputTexture) { - outputTexture = new FilterTexture(this.renderer.gl, this.width, this.height); - } - outputTexture.resize(this.width, this.height); - - // need to clear this FBO as it may have some left over elements from a previous filter. - gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); - gl.clear(gl.COLOR_BUFFER_BIT); - - gl.disable(gl.BLEND); - - for (var i = 0; i < filterBlock.filterPasses.length-1; i++) { - var filterPass = filterBlock.filterPasses[i]; - - gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); - - // set texture - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); - - // draw texture.. - //filterPass.applyFilterPass(filterArea.width, filterArea.height); - this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height); - - // swap the textures.. - var temp = inputTexture; - inputTexture = outputTexture; - outputTexture = temp; - } - - gl.enable(gl.BLEND); - - texture = inputTexture; - this.texturePool.push(outputTexture); - } - - var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1]; - - this.offsetX -= filterArea.x; - this.offsetY -= filterArea.y; - - var sizeX = this.width; - var sizeY = this.height; - - var offsetX = 0; - var offsetY = 0; - - var buffer = this.buffer; - - // time to render the filters texture to the previous scene - if (this.filterStack.length === 0) { - gl.colorMask(true, true, true, true);//this.transparent); - } - else { - var currentFilter = this.filterStack[this.filterStack.length-1]; - filterArea = currentFilter._filterArea; - - sizeX = filterArea.width; - sizeY = filterArea.height; - - offsetX = filterArea.x; - offsetY = filterArea.y; - - buffer = currentFilter._glFilterTexture.frameBuffer; - } - - // TODO need to remove these global elements.. - projection.x = sizeX/2; - projection.y = -sizeY/2; - - offset.x = offsetX; - offset.y = offsetY; - - filterArea = filterBlock._filterArea; - - var x = filterArea.x-offsetX; - var y = filterArea.y-offsetY; - - // update the buffers.. - // make sure to flip the y! - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - - this.vertexArray[0] = x; - this.vertexArray[1] = y + filterArea.height; - - this.vertexArray[2] = x + filterArea.width; - this.vertexArray[3] = y + filterArea.height; - - this.vertexArray[4] = x; - this.vertexArray[5] = y; - - this.vertexArray[6] = x + filterArea.width; - this.vertexArray[7] = y; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - - this.uvArray[2] = filterArea.width/this.width; - this.uvArray[5] = filterArea.height/this.height; - this.uvArray[6] = filterArea.width/this.width; - this.uvArray[7] = filterArea.height/this.height; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); - - gl.viewport(0, 0, sizeX, sizeY); - - // bind the buffer - gl.bindFramebuffer(gl.FRAMEBUFFER, buffer ); - - // set the blend mode! - //gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) - - // set texture - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, texture.texture); - - // apply! - this.applyFilterPass(filter, filterArea, sizeX, sizeY); - - // now restore the regular shader.. should happen automatically now.. - // this.renderer.shaderManager.setShader(this.defaultShader); - // gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2); - // gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY); - - // return the texture to the pool - this.texturePool.push(texture); - filterBlock._glFilterTexture = null; -}; - - -/** - * Applies the filter to the specified area. - * - * @param filter {AbstractFilter} the filter that needs to be applied - * @param filterArea {Texture} TODO - might need an update - * @param width {number} the horizontal range of the filter - * @param height {number} the vertical range of the filter - */ -WebGLFilterManager.prototype.applyFilterPass = function (filter, filterArea, width, height) { - // use program - var gl = this.renderer.gl; - - var shader = filter.shaders[gl.id]; - - if (!shader) { - shader = new PixiShader(gl); - - shader.fragmentSrc = filter.fragmentSrc; - shader.uniforms = filter.uniforms; - shader.init(); - - filter.shaders[gl.id] = shader; - } - - // set the shader - this.renderer.shaderManager.setShader(shader); - -// gl.useProgram(shader.program); - - gl.uniform2f(shader.projectionVector, width/2, -height/2); - gl.uniform2f(shader.offsetVector, 0,0); - - if (filter.uniforms.dimensions) { - filter.uniforms.dimensions.value[0] = this.width;//width; - filter.uniforms.dimensions.value[1] = this.height;//height; - filter.uniforms.dimensions.value[2] = this.vertexArray[0]; - filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; - } - - shader.syncUniforms(); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); - gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - - // draw the filter... - gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); - - this.renderer.drawCount++; -}; - -/** - * Initialises the shader buffers. - * - */ -WebGLFilterManager.prototype.initShaderBuffers = function () { - var gl = this.renderer.gl; - - // create some buffers - this.vertexBuffer = gl.createBuffer(); - this.uvBuffer = gl.createBuffer(); - this.colorBuffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - - // bind and upload the vertexs.. - // keep a reference to the vertexFloatData.. - this.vertexArray = new Float32Array([0.0, 0.0, - 1.0, 0.0, - 0.0, 1.0, - 1.0, 1.0]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW); - - // bind and upload the uv buffer - this.uvArray = new Float32Array([0.0, 0.0, - 1.0, 0.0, - 0.0, 1.0, - 1.0, 1.0]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.uvArray, gl.STATIC_DRAW); - - this.colorArray = new Float32Array([1.0, 0xFFFFFF, - 1.0, 0xFFFFFF, - 1.0, 0xFFFFFF, - 1.0, 0xFFFFFF]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.colorArray, gl.STATIC_DRAW); - - // bind and upload the index - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW); - -}; - -/** - * Destroys the filter and removes it from the filter stack. - * - */ -WebGLFilterManager.prototype.destroy = function () { - var gl = this.renderer.gl; - - this.filterStack = null; - - this.offsetX = 0; - this.offsetY = 0; - - // destroy textures - for (var i = 0; i < this.texturePool.length; i++) { - this.texturePool[i].destroy(); - } - - this.texturePool = null; - - //destroy buffers.. - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.uvBuffer); - gl.deleteBuffer(this.colorBuffer); - gl.deleteBuffer(this.indexBuffer); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/WebGLGraphics.js b/src/core/renderers/webgl/utils/WebGLGraphics.js index 09f540f..1d1fe40 100644 --- a/src/core/renderers/webgl/utils/WebGLGraphics.js +++ b/src/core/renderers/webgl/utils/WebGLGraphics.js @@ -1,6 +1,5 @@ var utils = require('../../../utils'), math = require('../../../math'), - Graphics = require('../../../../primitives/Graphics'), WebGLGraphicsData = require('./WebGLGraphicsData'); /** @@ -122,7 +121,7 @@ for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) { var data = graphics.graphicsData[i]; - if (data.type === Graphics.POLY) { + 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) { @@ -164,13 +163,13 @@ else { webGLData = WebGLGraphics.switchMode(webGL, 0); - if (data.type === Graphics.RECT) { + if (data.type === CONST.SHAPES.RECT) { WebGLGraphics.buildRectangle(data, webGLData); } - else if (data.type === Graphics.CIRC || data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) { WebGLGraphics.buildCircle(data, webGLData); } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { WebGLGraphics.buildRoundedRectangle(data, webGLData); } } @@ -415,7 +414,7 @@ var height; // TODO - bit hacky?? - if (graphicsData.type === Graphics.CIRC) { + if (graphicsData.type === CONST.SHAPES.CIRC) { width = circleData.radius; height = circleData.radius; } diff --git a/src/core/renderers/webgl/utils/WebGLManager.js b/src/core/renderers/webgl/utils/WebGLManager.js deleted file mode 100644 index 9d47b5c..0000000 --- a/src/core/renderers/webgl/utils/WebGLManager.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLManager(renderer) { - /** - * The renderer this manager works for. - * - * @member {WebGLRenderer} - */ - this.renderer = renderer; -} - -WebGLManager.prototype.constructor = WebGLManager; -module.exports = WebGLManager; - -WebGLManager.prototype.destroy = function () { - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/WebGLMaskManager.js b/src/core/renderers/webgl/utils/WebGLMaskManager.js deleted file mode 100644 index 6d46d85..0000000 --- a/src/core/renderers/webgl/utils/WebGLMaskManager.js +++ /dev/null @@ -1,41 +0,0 @@ -var WebGLManager = require('./WebGLManager'), - WebGLGraphics = require('./WebGLGraphics'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLMaskManager(renderer) { - WebGLManager.call(this, renderer); -} - -WebGLMaskManager.prototype = Object.create(WebGLManager.prototype); -WebGLMaskManager.prototype.constructor = WebGLMaskManager; -module.exports = WebGLMaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {any[]} - */ -WebGLMaskManager.prototype.pushMask = function (maskData) { - if (maskData.dirty) { - WebGLGraphics.updateGraphics(maskData, this.renderer.gl); - } - - if (!maskData._webGL[this.renderer.gl.id].data.length) { - return; - } - - this.renderer.stencilManager.pushStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); -}; - -/** - * Removes the last filter from the filter stack and doesn't return it. - * - * @param maskData {any[]} - */ -WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.stencilManager.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); -}; diff --git a/src/core/const.js b/src/core/const.js index 2803e5b..aea72ec 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -130,5 +130,25 @@ backgroundColor: 0x000000, clearBeforeRender: true, preserveDrawingBuffer: false + }, + + /** + * Constants that identify shapes, mainly to prevent `instanceof` calls. + * + * @static + * @constant + * @property {object} SHAPES + * @property {object} SHAPES.POLY=0 + * @property {object} SHAPES.RECT=1 + * @property {object} SHAPES.CIRC=2 + * @property {object} SHAPES.ELIP=3 + * @property {object} SHAPES.RREC=4 + */ + SHAPES: { + POLY = 0; + RECT = 1; + CIRC = 2; + ELIP = 3; + RREC = 4; } }; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index bfd59cf..7c0e7c6 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -95,7 +95,7 @@ this.filterArea = null; /** - * cached sin rotation and cos rotation + * cached sin rotation * * @member {number} * @private @@ -103,7 +103,7 @@ this._sr = 0; /** - * cached sin rotation and cos rotation + * cached cos rotation * * @member {number} * @private @@ -400,7 +400,7 @@ DisplayObject.prototype.generateTexture = function (resolution, scaleMode, renderer) { var bounds = this.getLocalBounds(); - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); _tempMatrix.tx = -bounds.x; _tempMatrix.ty = -bounds.y; @@ -474,7 +474,7 @@ var bounds = this.getLocalBounds(); if (!this._cachedSprite) { - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0); //, renderer); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0); this._cachedSprite = new Sprite(renderTexture); this._cachedSprite.worldTransform = this.worldTransform; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 3f6a3b6..69cfdc9 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Circle object can be used to specify a hit area for displayObjects @@ -29,10 +30,11 @@ this.radius = radius || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.CIRC in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.CIRC; } Circle.prototype.constructor = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index fc9df4d..4f2d066 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Ellipse object can be used to specify a hit area for displayObjects @@ -36,10 +37,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.ELIP in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.ELIP; } Ellipse.prototype.constructor = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index 5d4a464..ae89148 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -1,4 +1,5 @@ -var Point = require('../Point'); +var Point = require('../Point'), + CONST = require('../../const'); /** * @class @@ -33,6 +34,13 @@ * @member {Point[]} */ this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + */ + this.type = CONST.SHAPES.POLY; } Polygon.prototype.constructor = Polygon; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index c3454b5..16ac62a 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * the Rectangle object is an area defined by its position, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -34,10 +36,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RECT; } Rectangle.prototype.constructor = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0f2abfc..27d286e 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * The Rounded Rectangle object is an area defined by its position and has nice rounded corners, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -41,10 +43,11 @@ this.radius = radius || 20; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RRECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RREC; } RoundedRectangle.prototype.constructor = RoundedRectangle; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js new file mode 100644 index 0000000..42647c5 --- /dev/null +++ b/src/core/primitives/Graphics.js @@ -0,0 +1,1029 @@ +var core = require('../core'), + GraphicsData = require('./GraphicsData'); + +/** + * 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() { + core.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 {object[]} + * @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 = core.CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {object} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object[]} + * @private + */ + 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 core.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(core.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; + } + } + } +}); + +/** + * 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 core.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 core.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; + + 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 core.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 core.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 core.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 core.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 core.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 core.CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = core.Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + core.CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @method _renderWebGL + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderWebGL = function (renderSession) { + // 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 on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + core.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); + + return; + } + else { + renderSession.spriteBatch.stop(); + renderSession.blendModeManager.setBlendMode(this.blendMode); + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + if (this._filters) { + renderSession.filterManager.pushFilter(this._filterBlock); + } + + // check blend mode + if (this.blendMode !== renderSession.spriteBatch.currentBlendMode) { + renderSession.spriteBatch.currentBlendMode = this.blendMode; + + var blendModeWebGL = blendModesWebGL[renderSession.spriteBatch.currentBlendMode]; + + renderSession.spriteBatch.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); + } + + // check if the webgl graphic needs to be updated + if (this.glDirty) { + this.dirty = true; + this.glDirty = false; + } + + core.WebGLGraphics.renderGraphics(this, renderSession); + + // only render if it has children! + if (this.children.length) { + renderSession.spriteBatch.start(); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderWebGL(renderSession); + } + + renderSession.spriteBatch.stop(); + } + + if (this._filters) { + renderSession.filterManager.popFilter(); + } + + if (this._mask) { + renderSession.maskManager.popMask(this.mask, renderSession); + } + + renderSession.drawCount++; + + renderSession.spriteBatch.start(); + } +}; + +/** + * Renders the object using the Canvas renderer + * + * @method _renderCanvas + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderCanvas = function (renderSession) { + // 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; + + core.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession); + + return; + } + else { + var context = renderSession.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderSession.currentBlendMode) { + renderSession.currentBlendMode = this.blendMode; + context.globalCompositeOperation = blendModesCanvas[renderSession.currentBlendMode]; + } + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + var resolution = renderSession.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + core.CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderCanvas(renderSession); + } + + if (this._mask) { + renderSession.maskManager.popMask(renderSession); + } + } +}; + +/** + * 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 core.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 === core.CONST.SHAPES.RECT || type === core.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 === core.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 === core.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 core.CanvasBuffer(bounds.width, bounds.height); + var texture = core.Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new core.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.. + core.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 === core.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 new file mode 100644 index 0000000..9377ef7 --- /dev/null +++ b/src/core/primitives/GraphicsData.js @@ -0,0 +1,23 @@ +/** + * 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; diff --git a/src/core/primitives/index.js b/src/core/primitives/index.js new file mode 100644 index 0000000..7f02649 --- /dev/null +++ b/src/core/primitives/index.js @@ -0,0 +1,14 @@ +/** + * @file Main export of the PIXI primitives library + * @author Mat Groves + * @copyright 2013-2015 GoodBoyDigital + * @license {@link https://github.com/GoodBoyDigital/pixi.js/blob/master/LICENSE|MIT License} + */ + +/** + * @namespace PIXI + */ +module.exports = { + Graphics: require('./Graphics'), + GraphicsData: require('./GraphicsData') +}; diff --git a/src/core/renderers/canvas/CanvasGraphics.js b/src/core/renderers/canvas/CanvasGraphics.js index 2b1797a..ef7e765 100644 --- a/src/core/renderers/canvas/CanvasGraphics.js +++ b/src/core/renderers/canvas/CanvasGraphics.js @@ -1,4 +1,4 @@ -var Graphics = require('../../../primitives/Graphics'); +var CONST = require('../../const'); /** * A set of functions used by the canvas renderer to draw the primitive graphics data. @@ -30,7 +30,7 @@ context.lineWidth = data.lineWidth; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -61,7 +61,7 @@ context.stroke(); } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { if (data.fillColor || data.fillColor === 0) { context.globalAlpha = data.fillAlpha * worldAlpha; @@ -75,7 +75,7 @@ context.strokeRect(shape.x, shape.y, shape.width, shape.height); } } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); @@ -92,7 +92,7 @@ context.stroke(); } } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas var w = shape.width * 2; @@ -130,7 +130,7 @@ context.stroke(); } } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var rx = shape.x; var ry = shape.y; var width = shape.width; @@ -190,7 +190,7 @@ var data = graphics.graphicsData[i]; var shape = data.shape; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -207,18 +207,18 @@ } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { context.beginPath(); context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); context.closePath(); } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas @@ -245,7 +245,7 @@ context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); context.closePath(); } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var pts = shape.points; var rx = pts[0]; diff --git a/src/core/renderers/webgl/managers/WebGLBlendModeManager.js b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js new file mode 100644 index 0000000..9f2f89a --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js @@ -0,0 +1,37 @@ +var WebGLManager = require('./WebGLManager'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLBlendModeManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; +} + +WebGLBlendModeManager.prototype = Object.create(WebGLManager.prototype); +WebGLBlendModeManager.prototype.constructor = WebGLBlendModeManager; +module.exports = WebGLBlendModeManager; + +/** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as BlendModes.ADD + */ +WebGLBlendModeManager.prototype.setBlendMode = function (blendMode) { + if (this.currentBlendMode === blendMode) { + return false; + } + + this.currentBlendMode = blendMode; + + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; +}; diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js new file mode 100644 index 0000000..176e7cd --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -0,0 +1,446 @@ +var WebGLManager = require('./WebGLManager'), + FilterTexture = require('./FilterTexture'), + PixiShader = require('../shaders/PixiShader'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLFilterManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {any[]} + */ + this.filterStack = []; + + /** + * @member {any[]]} + */ + this.texturePool = []; + + /** + * @member {number} + */ + this.offsetX = 0; + + /** + * @member {number} + */ + this.offsetY = 0; + + // listen for context and update necessary buffers + var self = this; + this.renderer.on('context', function () { + self.texturePool.length = 0; + self.initShaderBuffers(); + }); +} + +WebGLFilterManager.prototype = Object.create(WebGLManager.prototype); +WebGLFilterManager.prototype.constructor = WebGLFilterManager; +module.exports = WebGLFilterManager; + +/** + * @param renderer {WebGLRenderer} + * @param buffer {ArrayBuffer} + */ +WebGLFilterManager.prototype.begin = function (renderer, buffer) { + this.renderer = renderer; + this.defaultShader = renderer.shaderManager.defaultShader; + + this.width = renderer.projection.x * 2; + this.height = -renderer.projection.y * 2; + + this.buffer = buffer; +}; + +/** + * Applies the filter and adds it to the current filter stack. + * + * @param filterBlock {object} the filter that will be pushed to the current filter stack + */ +WebGLFilterManager.prototype.pushFilter = function (filterBlock) { + var gl = this.renderer.gl; + + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds(); + + // filter program + // OPTIMISATION - the first filter is free if its a simple color change? + this.filterStack.push(filterBlock); + + var filter = filterBlock.filterPasses[0]; + + this.offsetX += filterBlock._filterArea.x; + this.offsetY += filterBlock._filterArea.y; + + var texture = this.texturePool.pop(); + if (!texture) { + texture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + else { + texture.resize(this.width, this.height); + } + + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea; + + var padding = filter.padding; + filterArea.x -= padding; + filterArea.y -= padding; + filterArea.width += padding * 2; + filterArea.height += padding * 2; + + // cap filter to screen size.. + if (filterArea.x < 0) { + filterArea.x = 0; + } + + if (filterArea.width > this.width) { + filterArea.width = this.width; + } + + if (filterArea.y < 0) { + filterArea.y = 0; + } + + if (filterArea.height > this.height) { + filterArea.height = this.height; + } + + //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); + + // set view port + gl.viewport(0, 0, filterArea.width, filterArea.height); + + projection.x = filterArea.width/2; + projection.y = -filterArea.height/2; + + offset.x = -filterArea.x; + offset.y = -filterArea.y; + + // update projection + // now restore the regular shader.. + // this.renderer.shaderManager.setShader(this.defaultShader); + //gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); + //gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y); + + gl.colorMask(true, true, true, true); + gl.clearColor(0,0,0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + + filterBlock._glFilterTexture = texture; + +}; + +/** + * Removes the last filter from the filter stack and doesn't return it. + * + */ +WebGLFilterManager.prototype.popFilter = function () { + var gl = this.renderer.gl; + + var filterBlock = this.filterStack.pop(); + var filterArea = filterBlock._filterArea; + var texture = filterBlock._glFilterTexture; + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + if (filterBlock.filterPasses.length > 1) { + gl.viewport(0, 0, filterArea.width, filterArea.height); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = 0; + this.vertexArray[1] = filterArea.height; + + this.vertexArray[2] = filterArea.width; + this.vertexArray[3] = filterArea.height; + + this.vertexArray[4] = 0; + this.vertexArray[5] = 0; + + this.vertexArray[6] = filterArea.width; + this.vertexArray[7] = 0; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + // now set the uvs.. + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + var inputTexture = texture; + var outputTexture = this.texturePool.pop(); + if (!outputTexture) { + outputTexture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + outputTexture.resize(this.width, this.height); + + // need to clear this FBO as it may have some left over elements from a previous filter. + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + gl.clear(gl.COLOR_BUFFER_BIT); + + gl.disable(gl.BLEND); + + for (var i = 0; i < filterBlock.filterPasses.length-1; i++) { + var filterPass = filterBlock.filterPasses[i]; + + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); + + // draw texture.. + //filterPass.applyFilterPass(filterArea.width, filterArea.height); + this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height); + + // swap the textures.. + var temp = inputTexture; + inputTexture = outputTexture; + outputTexture = temp; + } + + gl.enable(gl.BLEND); + + texture = inputTexture; + this.texturePool.push(outputTexture); + } + + var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1]; + + this.offsetX -= filterArea.x; + this.offsetY -= filterArea.y; + + var sizeX = this.width; + var sizeY = this.height; + + var offsetX = 0; + var offsetY = 0; + + var buffer = this.buffer; + + // time to render the filters texture to the previous scene + if (this.filterStack.length === 0) { + gl.colorMask(true, true, true, true);//this.transparent); + } + else { + var currentFilter = this.filterStack[this.filterStack.length-1]; + filterArea = currentFilter._filterArea; + + sizeX = filterArea.width; + sizeY = filterArea.height; + + offsetX = filterArea.x; + offsetY = filterArea.y; + + buffer = currentFilter._glFilterTexture.frameBuffer; + } + + // TODO need to remove these global elements.. + projection.x = sizeX/2; + projection.y = -sizeY/2; + + offset.x = offsetX; + offset.y = offsetY; + + filterArea = filterBlock._filterArea; + + var x = filterArea.x-offsetX; + var y = filterArea.y-offsetY; + + // update the buffers.. + // make sure to flip the y! + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = x; + this.vertexArray[1] = y + filterArea.height; + + this.vertexArray[2] = x + filterArea.width; + this.vertexArray[3] = y + filterArea.height; + + this.vertexArray[4] = x; + this.vertexArray[5] = y; + + this.vertexArray[6] = x + filterArea.width; + this.vertexArray[7] = y; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + gl.viewport(0, 0, sizeX, sizeY); + + // bind the buffer + gl.bindFramebuffer(gl.FRAMEBUFFER, buffer ); + + // set the blend mode! + //gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + // apply! + this.applyFilterPass(filter, filterArea, sizeX, sizeY); + + // now restore the regular shader.. should happen automatically now.. + // this.renderer.shaderManager.setShader(this.defaultShader); + // gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2); + // gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY); + + // return the texture to the pool + this.texturePool.push(texture); + filterBlock._glFilterTexture = null; +}; + + +/** + * Applies the filter to the specified area. + * + * @param filter {AbstractFilter} the filter that needs to be applied + * @param filterArea {Texture} TODO - might need an update + * @param width {number} the horizontal range of the filter + * @param height {number} the vertical range of the filter + */ +WebGLFilterManager.prototype.applyFilterPass = function (filter, filterArea, width, height) { + // use program + var gl = this.renderer.gl; + + var shader = filter.shaders[gl.id]; + + if (!shader) { + shader = new PixiShader(gl); + + shader.fragmentSrc = filter.fragmentSrc; + shader.uniforms = filter.uniforms; + shader.init(); + + filter.shaders[gl.id] = shader; + } + + // set the shader + this.renderer.shaderManager.setShader(shader); + +// gl.useProgram(shader.program); + + gl.uniform2f(shader.projectionVector, width/2, -height/2); + gl.uniform2f(shader.offsetVector, 0,0); + + if (filter.uniforms.dimensions) { + filter.uniforms.dimensions.value[0] = this.width;//width; + filter.uniforms.dimensions.value[1] = this.height;//height; + filter.uniforms.dimensions.value[2] = this.vertexArray[0]; + filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; + } + + shader.syncUniforms(); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + + // draw the filter... + gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); + + this.renderer.drawCount++; +}; + +/** + * Initialises the shader buffers. + * + */ +WebGLFilterManager.prototype.initShaderBuffers = function () { + var gl = this.renderer.gl; + + // create some buffers + this.vertexBuffer = gl.createBuffer(); + this.uvBuffer = gl.createBuffer(); + this.colorBuffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + + // bind and upload the vertexs.. + // keep a reference to the vertexFloatData.. + this.vertexArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW); + + // bind and upload the uv buffer + this.uvArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.uvArray, gl.STATIC_DRAW); + + this.colorArray = new Float32Array([1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.colorArray, gl.STATIC_DRAW); + + // bind and upload the index + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW); + +}; + +/** + * Destroys the filter and removes it from the filter stack. + * + */ +WebGLFilterManager.prototype.destroy = function () { + var gl = this.renderer.gl; + + this.filterStack = null; + + this.offsetX = 0; + this.offsetY = 0; + + // destroy textures + for (var i = 0; i < this.texturePool.length; i++) { + this.texturePool[i].destroy(); + } + + this.texturePool = null; + + //destroy buffers.. + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.uvBuffer); + gl.deleteBuffer(this.colorBuffer); + gl.deleteBuffer(this.indexBuffer); + + this.renderer = null; +}; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js new file mode 100644 index 0000000..9d47b5c --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -0,0 +1,20 @@ +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLManager(renderer) { + /** + * The renderer this manager works for. + * + * @member {WebGLRenderer} + */ + this.renderer = renderer; +} + +WebGLManager.prototype.constructor = WebGLManager; +module.exports = WebGLManager; + +WebGLManager.prototype.destroy = function () { + this.renderer = null; +}; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js new file mode 100644 index 0000000..6d46d85 --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -0,0 +1,41 @@ +var WebGLManager = require('./WebGLManager'), + WebGLGraphics = require('./WebGLGraphics'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLMaskManager(renderer) { + WebGLManager.call(this, renderer); +} + +WebGLMaskManager.prototype = Object.create(WebGLManager.prototype); +WebGLMaskManager.prototype.constructor = WebGLMaskManager; +module.exports = WebGLMaskManager; + +/** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {any[]} + */ +WebGLMaskManager.prototype.pushMask = function (maskData) { + if (maskData.dirty) { + WebGLGraphics.updateGraphics(maskData, this.renderer.gl); + } + + if (!maskData._webGL[this.renderer.gl.id].data.length) { + return; + } + + this.renderer.stencilManager.pushStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); +}; + +/** + * Removes the last filter from the filter stack and doesn't return it. + * + * @param maskData {any[]} + */ +WebGLMaskManager.prototype.popMask = function (maskData) { + this.renderer.stencilManager.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 new file mode 100644 index 0000000..d02d95d --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -0,0 +1,172 @@ +var WebGLManager = require('./WebGLManager'), + PrimitiveShader = require('../shaders/PrimitiveShader'), + ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), + PixiShader = require('../shaders/PixiShader'), + PixiFastShader = require('../shaders/PixiFastShader'), + StripShader = require('../shaders/StripShader'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLShaderManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {number} + */ + this.maxAttibs = 10; + + /** + * @member {any[]} + */ + this.attribState = []; + + /** + * @member {any[]} + */ + this.tempAttribState = []; + + for (var i = 0; i < this.maxAttibs; i++) { + this.attribState[i] = false; + } + + /** + * @member {any[]} + */ + this.stack = []; + + /** + * @member {number} + * @private + */ + this._currentId = -1; + + /** + * @member {Shader} + * @private + */ + 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; + + // listen for context and update necessary shaders + var self = this; + this.renderer.on('context', function (gl) { + // 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 PixiShader(gl); + + // this shader is used for the fast sprite rendering + self.fastShader = new PixiFastShader(gl); + + // the next one is used for rendering triangle strips + self.stripShader = new StripShader(gl); + + self.setShader(self.defaultShader); + }); +} + +WebGLShaderManager.prototype = Object.create(WebGLManager.prototype); +WebGLShaderManager.prototype.constructor = WebGLShaderManager; +module.exports = WebGLShaderManager; + +/** + * Takes the attributes given in parameters. + * + * @param attribs {Array} attribs + */ +WebGLShaderManager.prototype.setAttribs = function (attribs) { + // reset temp state + var i; + + for (i = 0; i < this.tempAttribState.length; i++) { + this.tempAttribState[i] = false; + } + + // set the new attribs + for (var a in attribs) { + this.tempAttribState[attribs[a]] = true; + } + + var gl = this.renderer.gl; + + for (i = 0; i < this.attribState.length; i++) { + if (this.attribState[i] !== this.tempAttribState[i]) { + this.attribState[i] = this.tempAttribState[i]; + + if (this.attribState[i]) { + gl.enableVertexAttribArray(i); + } + else { + gl.disableVertexAttribArray(i); + } + } + } +}; + +/** + * Sets the current shader. + * + * @param shader {Any} + */ +WebGLShaderManager.prototype.setShader = function (shader) { + if (this._currentId === shader.uuid) { + return false; + } + + this._currentId = shader.uuid; + + this.currentShader = shader; + + this.renderer.gl.useProgram(shader.program); + this.setAttribs(shader.attributes); + + return true; +}; + +/** + * Destroys this object. + * + */ +WebGLShaderManager.prototype.destroy = function () { + 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/managers/WebGLStencilManager.js b/src/core/renderers/webgl/managers/WebGLStencilManager.js new file mode 100644 index 0000000..0bedae6 --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLStencilManager.js @@ -0,0 +1,256 @@ +var WebGLManager = require('./WebGLManager'), + utils = require('../../../utils'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLStencilManager(renderer) { + WebGLManager.call(this, renderer); + + this.stencilStack = []; + this.reverse = true; + this.count = 0; +} + +WebGLStencilManager.prototype = Object.create(WebGLManager.prototype); +WebGLStencilManager.prototype.constructor = WebGLStencilManager; +module.exports = WebGLStencilManager; + +/** + * Applies the Mask and adds it to the current filter stack. + * + * @param graphics {Graphics} + * @param webGLData {any[]} + */ +WebGLStencilManager.prototype.pushStencil = function (graphics, webGLData) { + var gl = this.renderer.gl; + + this.bindGraphics(graphics, webGLData, this.renderer); + + if (this.stencilStack.length === 0) { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + this.reverse = true; + this.count = 0; + } + + this.stencilStack.push(webGLData); + + var level = this.count; + + gl.colorMask(false, false, false, false); + + gl.stencilFunc(gl.ALWAYS,0,0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); + + // draw the triangle strip! + + if (webGLData.mode === 1) { + gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + + // draw a quad to increment.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + } + + this.reverse = !this.reverse; + } + else { + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + } + } + + gl.colorMask(true, true, true, true); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + + this.count++; +}; + +/** + * TODO this does not belong here! + * + * @param graphics {Graphics} + * @param webGLData {Array} + */ +WebGLStencilManager.prototype.bindGraphics = function (graphics, webGLData) { + //if (this._currentGraphics === graphics)return; + this._currentGraphics = graphics; + + var gl = this.renderer.gl; + + // bind the graphics object.. + var projection = this.renderer.projection, + offset = this.renderer.offset, + shader;// = this.renderer.shaderManager.primitiveShader; + + if (webGLData.mode === 1) { + shader = this.renderer.shaderManager.complexPrimitiveShader; + + this.renderer.shaderManager.setShader(shader); + + gl.uniform1f(shader.flipY, this.renderer.flipY); + + gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); + + gl.uniform2f(shader.projectionVector, projection.x, -projection.y); + gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); + + gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); + gl.uniform3fv(shader.color, webGLData.color); + + gl.uniform1f(shader.alpha, graphics.worldAlpha * webGLData.alpha); + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 2, 0); + + + // now do the rest.. + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + } + else { + //this.renderer.shaderManager.activatePrimitiveShader(); + shader = this.renderer.shaderManager.primitiveShader; + this.renderer.shaderManager.setShader( shader ); + + gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); + + gl.uniform1f(shader.flipY, this.renderer.flipY); + gl.uniform2f(shader.projectionVector, projection.x, -projection.y); + gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); + + gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); + + gl.uniform1f(shader.alpha, graphics.worldAlpha); + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); + gl.vertexAttribPointer(shader.colorAttribute, 4, gl.FLOAT, false,4 * 6, 2 * 4); + + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + } +}; + +/** + * @param graphics {Graphics} + * @param webGLData {Array} + */ +WebGLStencilManager.prototype.popStencil = function (graphics, webGLData) { + var gl = this.renderer.gl; + + this.stencilStack.pop(); + + this.count--; + + if (this.stencilStack.length === 0) { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + + } + else { + + var level = this.count; + + this.bindGraphics(graphics, webGLData, this.renderer); + + gl.colorMask(false, false, false, false); + + if (webGLData.mode === 1) { + this.reverse = !this.reverse; + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + + // draw a quad to increment.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + gl.stencilFunc(gl.ALWAYS,0,0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); + + // draw the triangle strip! + gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + } + + } + else { + // console.log("<<>>") + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + } + } + + gl.colorMask(true, true, true, true); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + + + } +}; + +/** + * Destroys the mask stack. + * + */ +WebGLStencilManager.prototype.destroy = function () { + this.renderer = null; + this.stencilStack = null; +}; diff --git a/src/core/renderers/webgl/utils/WebGLBlendModeManager.js b/src/core/renderers/webgl/utils/WebGLBlendModeManager.js deleted file mode 100644 index 9f2f89a..0000000 --- a/src/core/renderers/webgl/utils/WebGLBlendModeManager.js +++ /dev/null @@ -1,37 +0,0 @@ -var WebGLManager = require('./WebGLManager'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLBlendModeManager(renderer) { - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -WebGLBlendModeManager.prototype = Object.create(WebGLManager.prototype); -WebGLBlendModeManager.prototype.constructor = WebGLBlendModeManager; -module.exports = WebGLBlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as BlendModes.ADD - */ -WebGLBlendModeManager.prototype.setBlendMode = function (blendMode) { - if (this.currentBlendMode === blendMode) { - return false; - } - - this.currentBlendMode = blendMode; - - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); - - return true; -}; diff --git a/src/core/renderers/webgl/utils/WebGLFilterManager.js b/src/core/renderers/webgl/utils/WebGLFilterManager.js deleted file mode 100644 index 176e7cd..0000000 --- a/src/core/renderers/webgl/utils/WebGLFilterManager.js +++ /dev/null @@ -1,446 +0,0 @@ -var WebGLManager = require('./WebGLManager'), - FilterTexture = require('./FilterTexture'), - PixiShader = require('../shaders/PixiShader'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLFilterManager(renderer) { - WebGLManager.call(this, renderer); - - /** - * @member {any[]} - */ - this.filterStack = []; - - /** - * @member {any[]]} - */ - this.texturePool = []; - - /** - * @member {number} - */ - this.offsetX = 0; - - /** - * @member {number} - */ - this.offsetY = 0; - - // listen for context and update necessary buffers - var self = this; - this.renderer.on('context', function () { - self.texturePool.length = 0; - self.initShaderBuffers(); - }); -} - -WebGLFilterManager.prototype = Object.create(WebGLManager.prototype); -WebGLFilterManager.prototype.constructor = WebGLFilterManager; -module.exports = WebGLFilterManager; - -/** - * @param renderer {WebGLRenderer} - * @param buffer {ArrayBuffer} - */ -WebGLFilterManager.prototype.begin = function (renderer, buffer) { - this.renderer = renderer; - this.defaultShader = renderer.shaderManager.defaultShader; - - this.width = renderer.projection.x * 2; - this.height = -renderer.projection.y * 2; - - this.buffer = buffer; -}; - -/** - * Applies the filter and adds it to the current filter stack. - * - * @param filterBlock {object} the filter that will be pushed to the current filter stack - */ -WebGLFilterManager.prototype.pushFilter = function (filterBlock) { - var gl = this.renderer.gl; - - var projection = this.renderer.projection; - var offset = this.renderer.offset; - - filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds(); - - // filter program - // OPTIMISATION - the first filter is free if its a simple color change? - this.filterStack.push(filterBlock); - - var filter = filterBlock.filterPasses[0]; - - this.offsetX += filterBlock._filterArea.x; - this.offsetY += filterBlock._filterArea.y; - - var texture = this.texturePool.pop(); - if (!texture) { - texture = new FilterTexture(this.renderer.gl, this.width, this.height); - } - else { - texture.resize(this.width, this.height); - } - - gl.bindTexture(gl.TEXTURE_2D, texture.texture); - - var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea; - - var padding = filter.padding; - filterArea.x -= padding; - filterArea.y -= padding; - filterArea.width += padding * 2; - filterArea.height += padding * 2; - - // cap filter to screen size.. - if (filterArea.x < 0) { - filterArea.x = 0; - } - - if (filterArea.width > this.width) { - filterArea.width = this.width; - } - - if (filterArea.y < 0) { - filterArea.y = 0; - } - - if (filterArea.height > this.height) { - filterArea.height = this.height; - } - - //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); - - // set view port - gl.viewport(0, 0, filterArea.width, filterArea.height); - - projection.x = filterArea.width/2; - projection.y = -filterArea.height/2; - - offset.x = -filterArea.x; - offset.y = -filterArea.y; - - // update projection - // now restore the regular shader.. - // this.renderer.shaderManager.setShader(this.defaultShader); - //gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); - //gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y); - - gl.colorMask(true, true, true, true); - gl.clearColor(0,0,0, 0); - gl.clear(gl.COLOR_BUFFER_BIT); - - filterBlock._glFilterTexture = texture; - -}; - -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -WebGLFilterManager.prototype.popFilter = function () { - var gl = this.renderer.gl; - - var filterBlock = this.filterStack.pop(); - var filterArea = filterBlock._filterArea; - var texture = filterBlock._glFilterTexture; - var projection = this.renderer.projection; - var offset = this.renderer.offset; - - if (filterBlock.filterPasses.length > 1) { - gl.viewport(0, 0, filterArea.width, filterArea.height); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - - this.vertexArray[0] = 0; - this.vertexArray[1] = filterArea.height; - - this.vertexArray[2] = filterArea.width; - this.vertexArray[3] = filterArea.height; - - this.vertexArray[4] = 0; - this.vertexArray[5] = 0; - - this.vertexArray[6] = filterArea.width; - this.vertexArray[7] = 0; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - // now set the uvs.. - this.uvArray[2] = filterArea.width/this.width; - this.uvArray[5] = filterArea.height/this.height; - this.uvArray[6] = filterArea.width/this.width; - this.uvArray[7] = filterArea.height/this.height; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); - - var inputTexture = texture; - var outputTexture = this.texturePool.pop(); - if (!outputTexture) { - outputTexture = new FilterTexture(this.renderer.gl, this.width, this.height); - } - outputTexture.resize(this.width, this.height); - - // need to clear this FBO as it may have some left over elements from a previous filter. - gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); - gl.clear(gl.COLOR_BUFFER_BIT); - - gl.disable(gl.BLEND); - - for (var i = 0; i < filterBlock.filterPasses.length-1; i++) { - var filterPass = filterBlock.filterPasses[i]; - - gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); - - // set texture - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); - - // draw texture.. - //filterPass.applyFilterPass(filterArea.width, filterArea.height); - this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height); - - // swap the textures.. - var temp = inputTexture; - inputTexture = outputTexture; - outputTexture = temp; - } - - gl.enable(gl.BLEND); - - texture = inputTexture; - this.texturePool.push(outputTexture); - } - - var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1]; - - this.offsetX -= filterArea.x; - this.offsetY -= filterArea.y; - - var sizeX = this.width; - var sizeY = this.height; - - var offsetX = 0; - var offsetY = 0; - - var buffer = this.buffer; - - // time to render the filters texture to the previous scene - if (this.filterStack.length === 0) { - gl.colorMask(true, true, true, true);//this.transparent); - } - else { - var currentFilter = this.filterStack[this.filterStack.length-1]; - filterArea = currentFilter._filterArea; - - sizeX = filterArea.width; - sizeY = filterArea.height; - - offsetX = filterArea.x; - offsetY = filterArea.y; - - buffer = currentFilter._glFilterTexture.frameBuffer; - } - - // TODO need to remove these global elements.. - projection.x = sizeX/2; - projection.y = -sizeY/2; - - offset.x = offsetX; - offset.y = offsetY; - - filterArea = filterBlock._filterArea; - - var x = filterArea.x-offsetX; - var y = filterArea.y-offsetY; - - // update the buffers.. - // make sure to flip the y! - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - - this.vertexArray[0] = x; - this.vertexArray[1] = y + filterArea.height; - - this.vertexArray[2] = x + filterArea.width; - this.vertexArray[3] = y + filterArea.height; - - this.vertexArray[4] = x; - this.vertexArray[5] = y; - - this.vertexArray[6] = x + filterArea.width; - this.vertexArray[7] = y; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - - this.uvArray[2] = filterArea.width/this.width; - this.uvArray[5] = filterArea.height/this.height; - this.uvArray[6] = filterArea.width/this.width; - this.uvArray[7] = filterArea.height/this.height; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); - - gl.viewport(0, 0, sizeX, sizeY); - - // bind the buffer - gl.bindFramebuffer(gl.FRAMEBUFFER, buffer ); - - // set the blend mode! - //gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) - - // set texture - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, texture.texture); - - // apply! - this.applyFilterPass(filter, filterArea, sizeX, sizeY); - - // now restore the regular shader.. should happen automatically now.. - // this.renderer.shaderManager.setShader(this.defaultShader); - // gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2); - // gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY); - - // return the texture to the pool - this.texturePool.push(texture); - filterBlock._glFilterTexture = null; -}; - - -/** - * Applies the filter to the specified area. - * - * @param filter {AbstractFilter} the filter that needs to be applied - * @param filterArea {Texture} TODO - might need an update - * @param width {number} the horizontal range of the filter - * @param height {number} the vertical range of the filter - */ -WebGLFilterManager.prototype.applyFilterPass = function (filter, filterArea, width, height) { - // use program - var gl = this.renderer.gl; - - var shader = filter.shaders[gl.id]; - - if (!shader) { - shader = new PixiShader(gl); - - shader.fragmentSrc = filter.fragmentSrc; - shader.uniforms = filter.uniforms; - shader.init(); - - filter.shaders[gl.id] = shader; - } - - // set the shader - this.renderer.shaderManager.setShader(shader); - -// gl.useProgram(shader.program); - - gl.uniform2f(shader.projectionVector, width/2, -height/2); - gl.uniform2f(shader.offsetVector, 0,0); - - if (filter.uniforms.dimensions) { - filter.uniforms.dimensions.value[0] = this.width;//width; - filter.uniforms.dimensions.value[1] = this.height;//height; - filter.uniforms.dimensions.value[2] = this.vertexArray[0]; - filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; - } - - shader.syncUniforms(); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); - gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - - // draw the filter... - gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); - - this.renderer.drawCount++; -}; - -/** - * Initialises the shader buffers. - * - */ -WebGLFilterManager.prototype.initShaderBuffers = function () { - var gl = this.renderer.gl; - - // create some buffers - this.vertexBuffer = gl.createBuffer(); - this.uvBuffer = gl.createBuffer(); - this.colorBuffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - - // bind and upload the vertexs.. - // keep a reference to the vertexFloatData.. - this.vertexArray = new Float32Array([0.0, 0.0, - 1.0, 0.0, - 0.0, 1.0, - 1.0, 1.0]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW); - - // bind and upload the uv buffer - this.uvArray = new Float32Array([0.0, 0.0, - 1.0, 0.0, - 0.0, 1.0, - 1.0, 1.0]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.uvArray, gl.STATIC_DRAW); - - this.colorArray = new Float32Array([1.0, 0xFFFFFF, - 1.0, 0xFFFFFF, - 1.0, 0xFFFFFF, - 1.0, 0xFFFFFF]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.colorArray, gl.STATIC_DRAW); - - // bind and upload the index - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW); - -}; - -/** - * Destroys the filter and removes it from the filter stack. - * - */ -WebGLFilterManager.prototype.destroy = function () { - var gl = this.renderer.gl; - - this.filterStack = null; - - this.offsetX = 0; - this.offsetY = 0; - - // destroy textures - for (var i = 0; i < this.texturePool.length; i++) { - this.texturePool[i].destroy(); - } - - this.texturePool = null; - - //destroy buffers.. - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.uvBuffer); - gl.deleteBuffer(this.colorBuffer); - gl.deleteBuffer(this.indexBuffer); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/WebGLGraphics.js b/src/core/renderers/webgl/utils/WebGLGraphics.js index 09f540f..1d1fe40 100644 --- a/src/core/renderers/webgl/utils/WebGLGraphics.js +++ b/src/core/renderers/webgl/utils/WebGLGraphics.js @@ -1,6 +1,5 @@ var utils = require('../../../utils'), math = require('../../../math'), - Graphics = require('../../../../primitives/Graphics'), WebGLGraphicsData = require('./WebGLGraphicsData'); /** @@ -122,7 +121,7 @@ for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) { var data = graphics.graphicsData[i]; - if (data.type === Graphics.POLY) { + 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) { @@ -164,13 +163,13 @@ else { webGLData = WebGLGraphics.switchMode(webGL, 0); - if (data.type === Graphics.RECT) { + if (data.type === CONST.SHAPES.RECT) { WebGLGraphics.buildRectangle(data, webGLData); } - else if (data.type === Graphics.CIRC || data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) { WebGLGraphics.buildCircle(data, webGLData); } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { WebGLGraphics.buildRoundedRectangle(data, webGLData); } } @@ -415,7 +414,7 @@ var height; // TODO - bit hacky?? - if (graphicsData.type === Graphics.CIRC) { + if (graphicsData.type === CONST.SHAPES.CIRC) { width = circleData.radius; height = circleData.radius; } diff --git a/src/core/renderers/webgl/utils/WebGLManager.js b/src/core/renderers/webgl/utils/WebGLManager.js deleted file mode 100644 index 9d47b5c..0000000 --- a/src/core/renderers/webgl/utils/WebGLManager.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLManager(renderer) { - /** - * The renderer this manager works for. - * - * @member {WebGLRenderer} - */ - this.renderer = renderer; -} - -WebGLManager.prototype.constructor = WebGLManager; -module.exports = WebGLManager; - -WebGLManager.prototype.destroy = function () { - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/WebGLMaskManager.js b/src/core/renderers/webgl/utils/WebGLMaskManager.js deleted file mode 100644 index 6d46d85..0000000 --- a/src/core/renderers/webgl/utils/WebGLMaskManager.js +++ /dev/null @@ -1,41 +0,0 @@ -var WebGLManager = require('./WebGLManager'), - WebGLGraphics = require('./WebGLGraphics'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLMaskManager(renderer) { - WebGLManager.call(this, renderer); -} - -WebGLMaskManager.prototype = Object.create(WebGLManager.prototype); -WebGLMaskManager.prototype.constructor = WebGLMaskManager; -module.exports = WebGLMaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {any[]} - */ -WebGLMaskManager.prototype.pushMask = function (maskData) { - if (maskData.dirty) { - WebGLGraphics.updateGraphics(maskData, this.renderer.gl); - } - - if (!maskData._webGL[this.renderer.gl.id].data.length) { - return; - } - - this.renderer.stencilManager.pushStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); -}; - -/** - * Removes the last filter from the filter stack and doesn't return it. - * - * @param maskData {any[]} - */ -WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.stencilManager.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); -}; diff --git a/src/core/renderers/webgl/utils/WebGLShaderManager.js b/src/core/renderers/webgl/utils/WebGLShaderManager.js deleted file mode 100644 index d02d95d..0000000 --- a/src/core/renderers/webgl/utils/WebGLShaderManager.js +++ /dev/null @@ -1,172 +0,0 @@ -var WebGLManager = require('./WebGLManager'), - PrimitiveShader = require('../shaders/PrimitiveShader'), - ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), - PixiShader = require('../shaders/PixiShader'), - PixiFastShader = require('../shaders/PixiFastShader'), - StripShader = require('../shaders/StripShader'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLShaderManager(renderer) { - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.maxAttibs = 10; - - /** - * @member {any[]} - */ - this.attribState = []; - - /** - * @member {any[]} - */ - this.tempAttribState = []; - - for (var i = 0; i < this.maxAttibs; i++) { - this.attribState[i] = false; - } - - /** - * @member {any[]} - */ - this.stack = []; - - /** - * @member {number} - * @private - */ - this._currentId = -1; - - /** - * @member {Shader} - * @private - */ - 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; - - // listen for context and update necessary shaders - var self = this; - this.renderer.on('context', function (gl) { - // 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 PixiShader(gl); - - // this shader is used for the fast sprite rendering - self.fastShader = new PixiFastShader(gl); - - // the next one is used for rendering triangle strips - self.stripShader = new StripShader(gl); - - self.setShader(self.defaultShader); - }); -} - -WebGLShaderManager.prototype = Object.create(WebGLManager.prototype); -WebGLShaderManager.prototype.constructor = WebGLShaderManager; -module.exports = WebGLShaderManager; - -/** - * Takes the attributes given in parameters. - * - * @param attribs {Array} attribs - */ -WebGLShaderManager.prototype.setAttribs = function (attribs) { - // reset temp state - var i; - - for (i = 0; i < this.tempAttribState.length; i++) { - this.tempAttribState[i] = false; - } - - // set the new attribs - for (var a in attribs) { - this.tempAttribState[attribs[a]] = true; - } - - var gl = this.renderer.gl; - - for (i = 0; i < this.attribState.length; i++) { - if (this.attribState[i] !== this.tempAttribState[i]) { - this.attribState[i] = this.tempAttribState[i]; - - if (this.attribState[i]) { - gl.enableVertexAttribArray(i); - } - else { - gl.disableVertexAttribArray(i); - } - } - } -}; - -/** - * Sets the current shader. - * - * @param shader {Any} - */ -WebGLShaderManager.prototype.setShader = function (shader) { - if (this._currentId === shader.uuid) { - return false; - } - - this._currentId = shader.uuid; - - this.currentShader = shader; - - this.renderer.gl.useProgram(shader.program); - this.setAttribs(shader.attributes); - - return true; -}; - -/** - * Destroys this object. - * - */ -WebGLShaderManager.prototype.destroy = function () { - 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/const.js b/src/core/const.js index 2803e5b..aea72ec 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -130,5 +130,25 @@ backgroundColor: 0x000000, clearBeforeRender: true, preserveDrawingBuffer: false + }, + + /** + * Constants that identify shapes, mainly to prevent `instanceof` calls. + * + * @static + * @constant + * @property {object} SHAPES + * @property {object} SHAPES.POLY=0 + * @property {object} SHAPES.RECT=1 + * @property {object} SHAPES.CIRC=2 + * @property {object} SHAPES.ELIP=3 + * @property {object} SHAPES.RREC=4 + */ + SHAPES: { + POLY = 0; + RECT = 1; + CIRC = 2; + ELIP = 3; + RREC = 4; } }; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index bfd59cf..7c0e7c6 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -95,7 +95,7 @@ this.filterArea = null; /** - * cached sin rotation and cos rotation + * cached sin rotation * * @member {number} * @private @@ -103,7 +103,7 @@ this._sr = 0; /** - * cached sin rotation and cos rotation + * cached cos rotation * * @member {number} * @private @@ -400,7 +400,7 @@ DisplayObject.prototype.generateTexture = function (resolution, scaleMode, renderer) { var bounds = this.getLocalBounds(); - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); _tempMatrix.tx = -bounds.x; _tempMatrix.ty = -bounds.y; @@ -474,7 +474,7 @@ var bounds = this.getLocalBounds(); if (!this._cachedSprite) { - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0); //, renderer); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0); this._cachedSprite = new Sprite(renderTexture); this._cachedSprite.worldTransform = this.worldTransform; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 3f6a3b6..69cfdc9 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Circle object can be used to specify a hit area for displayObjects @@ -29,10 +30,11 @@ this.radius = radius || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.CIRC in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.CIRC; } Circle.prototype.constructor = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index fc9df4d..4f2d066 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Ellipse object can be used to specify a hit area for displayObjects @@ -36,10 +37,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.ELIP in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.ELIP; } Ellipse.prototype.constructor = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index 5d4a464..ae89148 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -1,4 +1,5 @@ -var Point = require('../Point'); +var Point = require('../Point'), + CONST = require('../../const'); /** * @class @@ -33,6 +34,13 @@ * @member {Point[]} */ this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + */ + this.type = CONST.SHAPES.POLY; } Polygon.prototype.constructor = Polygon; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index c3454b5..16ac62a 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * the Rectangle object is an area defined by its position, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -34,10 +36,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RECT; } Rectangle.prototype.constructor = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0f2abfc..27d286e 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * The Rounded Rectangle object is an area defined by its position and has nice rounded corners, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -41,10 +43,11 @@ this.radius = radius || 20; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RRECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RREC; } RoundedRectangle.prototype.constructor = RoundedRectangle; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js new file mode 100644 index 0000000..42647c5 --- /dev/null +++ b/src/core/primitives/Graphics.js @@ -0,0 +1,1029 @@ +var core = require('../core'), + GraphicsData = require('./GraphicsData'); + +/** + * 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() { + core.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 {object[]} + * @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 = core.CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {object} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object[]} + * @private + */ + 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 core.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(core.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; + } + } + } +}); + +/** + * 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 core.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 core.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; + + 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 core.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 core.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 core.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 core.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 core.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 core.CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = core.Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + core.CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @method _renderWebGL + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderWebGL = function (renderSession) { + // 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 on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + core.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); + + return; + } + else { + renderSession.spriteBatch.stop(); + renderSession.blendModeManager.setBlendMode(this.blendMode); + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + if (this._filters) { + renderSession.filterManager.pushFilter(this._filterBlock); + } + + // check blend mode + if (this.blendMode !== renderSession.spriteBatch.currentBlendMode) { + renderSession.spriteBatch.currentBlendMode = this.blendMode; + + var blendModeWebGL = blendModesWebGL[renderSession.spriteBatch.currentBlendMode]; + + renderSession.spriteBatch.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); + } + + // check if the webgl graphic needs to be updated + if (this.glDirty) { + this.dirty = true; + this.glDirty = false; + } + + core.WebGLGraphics.renderGraphics(this, renderSession); + + // only render if it has children! + if (this.children.length) { + renderSession.spriteBatch.start(); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderWebGL(renderSession); + } + + renderSession.spriteBatch.stop(); + } + + if (this._filters) { + renderSession.filterManager.popFilter(); + } + + if (this._mask) { + renderSession.maskManager.popMask(this.mask, renderSession); + } + + renderSession.drawCount++; + + renderSession.spriteBatch.start(); + } +}; + +/** + * Renders the object using the Canvas renderer + * + * @method _renderCanvas + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderCanvas = function (renderSession) { + // 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; + + core.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession); + + return; + } + else { + var context = renderSession.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderSession.currentBlendMode) { + renderSession.currentBlendMode = this.blendMode; + context.globalCompositeOperation = blendModesCanvas[renderSession.currentBlendMode]; + } + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + var resolution = renderSession.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + core.CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderCanvas(renderSession); + } + + if (this._mask) { + renderSession.maskManager.popMask(renderSession); + } + } +}; + +/** + * 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 core.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 === core.CONST.SHAPES.RECT || type === core.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 === core.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 === core.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 core.CanvasBuffer(bounds.width, bounds.height); + var texture = core.Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new core.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.. + core.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 === core.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 new file mode 100644 index 0000000..9377ef7 --- /dev/null +++ b/src/core/primitives/GraphicsData.js @@ -0,0 +1,23 @@ +/** + * 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; diff --git a/src/core/primitives/index.js b/src/core/primitives/index.js new file mode 100644 index 0000000..7f02649 --- /dev/null +++ b/src/core/primitives/index.js @@ -0,0 +1,14 @@ +/** + * @file Main export of the PIXI primitives library + * @author Mat Groves + * @copyright 2013-2015 GoodBoyDigital + * @license {@link https://github.com/GoodBoyDigital/pixi.js/blob/master/LICENSE|MIT License} + */ + +/** + * @namespace PIXI + */ +module.exports = { + Graphics: require('./Graphics'), + GraphicsData: require('./GraphicsData') +}; diff --git a/src/core/renderers/canvas/CanvasGraphics.js b/src/core/renderers/canvas/CanvasGraphics.js index 2b1797a..ef7e765 100644 --- a/src/core/renderers/canvas/CanvasGraphics.js +++ b/src/core/renderers/canvas/CanvasGraphics.js @@ -1,4 +1,4 @@ -var Graphics = require('../../../primitives/Graphics'); +var CONST = require('../../const'); /** * A set of functions used by the canvas renderer to draw the primitive graphics data. @@ -30,7 +30,7 @@ context.lineWidth = data.lineWidth; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -61,7 +61,7 @@ context.stroke(); } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { if (data.fillColor || data.fillColor === 0) { context.globalAlpha = data.fillAlpha * worldAlpha; @@ -75,7 +75,7 @@ context.strokeRect(shape.x, shape.y, shape.width, shape.height); } } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); @@ -92,7 +92,7 @@ context.stroke(); } } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas var w = shape.width * 2; @@ -130,7 +130,7 @@ context.stroke(); } } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var rx = shape.x; var ry = shape.y; var width = shape.width; @@ -190,7 +190,7 @@ var data = graphics.graphicsData[i]; var shape = data.shape; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -207,18 +207,18 @@ } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { context.beginPath(); context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); context.closePath(); } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas @@ -245,7 +245,7 @@ context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); context.closePath(); } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var pts = shape.points; var rx = pts[0]; diff --git a/src/core/renderers/webgl/managers/WebGLBlendModeManager.js b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js new file mode 100644 index 0000000..9f2f89a --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js @@ -0,0 +1,37 @@ +var WebGLManager = require('./WebGLManager'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLBlendModeManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; +} + +WebGLBlendModeManager.prototype = Object.create(WebGLManager.prototype); +WebGLBlendModeManager.prototype.constructor = WebGLBlendModeManager; +module.exports = WebGLBlendModeManager; + +/** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as BlendModes.ADD + */ +WebGLBlendModeManager.prototype.setBlendMode = function (blendMode) { + if (this.currentBlendMode === blendMode) { + return false; + } + + this.currentBlendMode = blendMode; + + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; +}; diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js new file mode 100644 index 0000000..176e7cd --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -0,0 +1,446 @@ +var WebGLManager = require('./WebGLManager'), + FilterTexture = require('./FilterTexture'), + PixiShader = require('../shaders/PixiShader'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLFilterManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {any[]} + */ + this.filterStack = []; + + /** + * @member {any[]]} + */ + this.texturePool = []; + + /** + * @member {number} + */ + this.offsetX = 0; + + /** + * @member {number} + */ + this.offsetY = 0; + + // listen for context and update necessary buffers + var self = this; + this.renderer.on('context', function () { + self.texturePool.length = 0; + self.initShaderBuffers(); + }); +} + +WebGLFilterManager.prototype = Object.create(WebGLManager.prototype); +WebGLFilterManager.prototype.constructor = WebGLFilterManager; +module.exports = WebGLFilterManager; + +/** + * @param renderer {WebGLRenderer} + * @param buffer {ArrayBuffer} + */ +WebGLFilterManager.prototype.begin = function (renderer, buffer) { + this.renderer = renderer; + this.defaultShader = renderer.shaderManager.defaultShader; + + this.width = renderer.projection.x * 2; + this.height = -renderer.projection.y * 2; + + this.buffer = buffer; +}; + +/** + * Applies the filter and adds it to the current filter stack. + * + * @param filterBlock {object} the filter that will be pushed to the current filter stack + */ +WebGLFilterManager.prototype.pushFilter = function (filterBlock) { + var gl = this.renderer.gl; + + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds(); + + // filter program + // OPTIMISATION - the first filter is free if its a simple color change? + this.filterStack.push(filterBlock); + + var filter = filterBlock.filterPasses[0]; + + this.offsetX += filterBlock._filterArea.x; + this.offsetY += filterBlock._filterArea.y; + + var texture = this.texturePool.pop(); + if (!texture) { + texture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + else { + texture.resize(this.width, this.height); + } + + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea; + + var padding = filter.padding; + filterArea.x -= padding; + filterArea.y -= padding; + filterArea.width += padding * 2; + filterArea.height += padding * 2; + + // cap filter to screen size.. + if (filterArea.x < 0) { + filterArea.x = 0; + } + + if (filterArea.width > this.width) { + filterArea.width = this.width; + } + + if (filterArea.y < 0) { + filterArea.y = 0; + } + + if (filterArea.height > this.height) { + filterArea.height = this.height; + } + + //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); + + // set view port + gl.viewport(0, 0, filterArea.width, filterArea.height); + + projection.x = filterArea.width/2; + projection.y = -filterArea.height/2; + + offset.x = -filterArea.x; + offset.y = -filterArea.y; + + // update projection + // now restore the regular shader.. + // this.renderer.shaderManager.setShader(this.defaultShader); + //gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); + //gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y); + + gl.colorMask(true, true, true, true); + gl.clearColor(0,0,0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + + filterBlock._glFilterTexture = texture; + +}; + +/** + * Removes the last filter from the filter stack and doesn't return it. + * + */ +WebGLFilterManager.prototype.popFilter = function () { + var gl = this.renderer.gl; + + var filterBlock = this.filterStack.pop(); + var filterArea = filterBlock._filterArea; + var texture = filterBlock._glFilterTexture; + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + if (filterBlock.filterPasses.length > 1) { + gl.viewport(0, 0, filterArea.width, filterArea.height); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = 0; + this.vertexArray[1] = filterArea.height; + + this.vertexArray[2] = filterArea.width; + this.vertexArray[3] = filterArea.height; + + this.vertexArray[4] = 0; + this.vertexArray[5] = 0; + + this.vertexArray[6] = filterArea.width; + this.vertexArray[7] = 0; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + // now set the uvs.. + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + var inputTexture = texture; + var outputTexture = this.texturePool.pop(); + if (!outputTexture) { + outputTexture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + outputTexture.resize(this.width, this.height); + + // need to clear this FBO as it may have some left over elements from a previous filter. + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + gl.clear(gl.COLOR_BUFFER_BIT); + + gl.disable(gl.BLEND); + + for (var i = 0; i < filterBlock.filterPasses.length-1; i++) { + var filterPass = filterBlock.filterPasses[i]; + + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); + + // draw texture.. + //filterPass.applyFilterPass(filterArea.width, filterArea.height); + this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height); + + // swap the textures.. + var temp = inputTexture; + inputTexture = outputTexture; + outputTexture = temp; + } + + gl.enable(gl.BLEND); + + texture = inputTexture; + this.texturePool.push(outputTexture); + } + + var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1]; + + this.offsetX -= filterArea.x; + this.offsetY -= filterArea.y; + + var sizeX = this.width; + var sizeY = this.height; + + var offsetX = 0; + var offsetY = 0; + + var buffer = this.buffer; + + // time to render the filters texture to the previous scene + if (this.filterStack.length === 0) { + gl.colorMask(true, true, true, true);//this.transparent); + } + else { + var currentFilter = this.filterStack[this.filterStack.length-1]; + filterArea = currentFilter._filterArea; + + sizeX = filterArea.width; + sizeY = filterArea.height; + + offsetX = filterArea.x; + offsetY = filterArea.y; + + buffer = currentFilter._glFilterTexture.frameBuffer; + } + + // TODO need to remove these global elements.. + projection.x = sizeX/2; + projection.y = -sizeY/2; + + offset.x = offsetX; + offset.y = offsetY; + + filterArea = filterBlock._filterArea; + + var x = filterArea.x-offsetX; + var y = filterArea.y-offsetY; + + // update the buffers.. + // make sure to flip the y! + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = x; + this.vertexArray[1] = y + filterArea.height; + + this.vertexArray[2] = x + filterArea.width; + this.vertexArray[3] = y + filterArea.height; + + this.vertexArray[4] = x; + this.vertexArray[5] = y; + + this.vertexArray[6] = x + filterArea.width; + this.vertexArray[7] = y; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + gl.viewport(0, 0, sizeX, sizeY); + + // bind the buffer + gl.bindFramebuffer(gl.FRAMEBUFFER, buffer ); + + // set the blend mode! + //gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + // apply! + this.applyFilterPass(filter, filterArea, sizeX, sizeY); + + // now restore the regular shader.. should happen automatically now.. + // this.renderer.shaderManager.setShader(this.defaultShader); + // gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2); + // gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY); + + // return the texture to the pool + this.texturePool.push(texture); + filterBlock._glFilterTexture = null; +}; + + +/** + * Applies the filter to the specified area. + * + * @param filter {AbstractFilter} the filter that needs to be applied + * @param filterArea {Texture} TODO - might need an update + * @param width {number} the horizontal range of the filter + * @param height {number} the vertical range of the filter + */ +WebGLFilterManager.prototype.applyFilterPass = function (filter, filterArea, width, height) { + // use program + var gl = this.renderer.gl; + + var shader = filter.shaders[gl.id]; + + if (!shader) { + shader = new PixiShader(gl); + + shader.fragmentSrc = filter.fragmentSrc; + shader.uniforms = filter.uniforms; + shader.init(); + + filter.shaders[gl.id] = shader; + } + + // set the shader + this.renderer.shaderManager.setShader(shader); + +// gl.useProgram(shader.program); + + gl.uniform2f(shader.projectionVector, width/2, -height/2); + gl.uniform2f(shader.offsetVector, 0,0); + + if (filter.uniforms.dimensions) { + filter.uniforms.dimensions.value[0] = this.width;//width; + filter.uniforms.dimensions.value[1] = this.height;//height; + filter.uniforms.dimensions.value[2] = this.vertexArray[0]; + filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; + } + + shader.syncUniforms(); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + + // draw the filter... + gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); + + this.renderer.drawCount++; +}; + +/** + * Initialises the shader buffers. + * + */ +WebGLFilterManager.prototype.initShaderBuffers = function () { + var gl = this.renderer.gl; + + // create some buffers + this.vertexBuffer = gl.createBuffer(); + this.uvBuffer = gl.createBuffer(); + this.colorBuffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + + // bind and upload the vertexs.. + // keep a reference to the vertexFloatData.. + this.vertexArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW); + + // bind and upload the uv buffer + this.uvArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.uvArray, gl.STATIC_DRAW); + + this.colorArray = new Float32Array([1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.colorArray, gl.STATIC_DRAW); + + // bind and upload the index + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW); + +}; + +/** + * Destroys the filter and removes it from the filter stack. + * + */ +WebGLFilterManager.prototype.destroy = function () { + var gl = this.renderer.gl; + + this.filterStack = null; + + this.offsetX = 0; + this.offsetY = 0; + + // destroy textures + for (var i = 0; i < this.texturePool.length; i++) { + this.texturePool[i].destroy(); + } + + this.texturePool = null; + + //destroy buffers.. + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.uvBuffer); + gl.deleteBuffer(this.colorBuffer); + gl.deleteBuffer(this.indexBuffer); + + this.renderer = null; +}; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js new file mode 100644 index 0000000..9d47b5c --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -0,0 +1,20 @@ +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLManager(renderer) { + /** + * The renderer this manager works for. + * + * @member {WebGLRenderer} + */ + this.renderer = renderer; +} + +WebGLManager.prototype.constructor = WebGLManager; +module.exports = WebGLManager; + +WebGLManager.prototype.destroy = function () { + this.renderer = null; +}; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js new file mode 100644 index 0000000..6d46d85 --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -0,0 +1,41 @@ +var WebGLManager = require('./WebGLManager'), + WebGLGraphics = require('./WebGLGraphics'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLMaskManager(renderer) { + WebGLManager.call(this, renderer); +} + +WebGLMaskManager.prototype = Object.create(WebGLManager.prototype); +WebGLMaskManager.prototype.constructor = WebGLMaskManager; +module.exports = WebGLMaskManager; + +/** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {any[]} + */ +WebGLMaskManager.prototype.pushMask = function (maskData) { + if (maskData.dirty) { + WebGLGraphics.updateGraphics(maskData, this.renderer.gl); + } + + if (!maskData._webGL[this.renderer.gl.id].data.length) { + return; + } + + this.renderer.stencilManager.pushStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); +}; + +/** + * Removes the last filter from the filter stack and doesn't return it. + * + * @param maskData {any[]} + */ +WebGLMaskManager.prototype.popMask = function (maskData) { + this.renderer.stencilManager.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 new file mode 100644 index 0000000..d02d95d --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -0,0 +1,172 @@ +var WebGLManager = require('./WebGLManager'), + PrimitiveShader = require('../shaders/PrimitiveShader'), + ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), + PixiShader = require('../shaders/PixiShader'), + PixiFastShader = require('../shaders/PixiFastShader'), + StripShader = require('../shaders/StripShader'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLShaderManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {number} + */ + this.maxAttibs = 10; + + /** + * @member {any[]} + */ + this.attribState = []; + + /** + * @member {any[]} + */ + this.tempAttribState = []; + + for (var i = 0; i < this.maxAttibs; i++) { + this.attribState[i] = false; + } + + /** + * @member {any[]} + */ + this.stack = []; + + /** + * @member {number} + * @private + */ + this._currentId = -1; + + /** + * @member {Shader} + * @private + */ + 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; + + // listen for context and update necessary shaders + var self = this; + this.renderer.on('context', function (gl) { + // 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 PixiShader(gl); + + // this shader is used for the fast sprite rendering + self.fastShader = new PixiFastShader(gl); + + // the next one is used for rendering triangle strips + self.stripShader = new StripShader(gl); + + self.setShader(self.defaultShader); + }); +} + +WebGLShaderManager.prototype = Object.create(WebGLManager.prototype); +WebGLShaderManager.prototype.constructor = WebGLShaderManager; +module.exports = WebGLShaderManager; + +/** + * Takes the attributes given in parameters. + * + * @param attribs {Array} attribs + */ +WebGLShaderManager.prototype.setAttribs = function (attribs) { + // reset temp state + var i; + + for (i = 0; i < this.tempAttribState.length; i++) { + this.tempAttribState[i] = false; + } + + // set the new attribs + for (var a in attribs) { + this.tempAttribState[attribs[a]] = true; + } + + var gl = this.renderer.gl; + + for (i = 0; i < this.attribState.length; i++) { + if (this.attribState[i] !== this.tempAttribState[i]) { + this.attribState[i] = this.tempAttribState[i]; + + if (this.attribState[i]) { + gl.enableVertexAttribArray(i); + } + else { + gl.disableVertexAttribArray(i); + } + } + } +}; + +/** + * Sets the current shader. + * + * @param shader {Any} + */ +WebGLShaderManager.prototype.setShader = function (shader) { + if (this._currentId === shader.uuid) { + return false; + } + + this._currentId = shader.uuid; + + this.currentShader = shader; + + this.renderer.gl.useProgram(shader.program); + this.setAttribs(shader.attributes); + + return true; +}; + +/** + * Destroys this object. + * + */ +WebGLShaderManager.prototype.destroy = function () { + 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/managers/WebGLStencilManager.js b/src/core/renderers/webgl/managers/WebGLStencilManager.js new file mode 100644 index 0000000..0bedae6 --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLStencilManager.js @@ -0,0 +1,256 @@ +var WebGLManager = require('./WebGLManager'), + utils = require('../../../utils'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLStencilManager(renderer) { + WebGLManager.call(this, renderer); + + this.stencilStack = []; + this.reverse = true; + this.count = 0; +} + +WebGLStencilManager.prototype = Object.create(WebGLManager.prototype); +WebGLStencilManager.prototype.constructor = WebGLStencilManager; +module.exports = WebGLStencilManager; + +/** + * Applies the Mask and adds it to the current filter stack. + * + * @param graphics {Graphics} + * @param webGLData {any[]} + */ +WebGLStencilManager.prototype.pushStencil = function (graphics, webGLData) { + var gl = this.renderer.gl; + + this.bindGraphics(graphics, webGLData, this.renderer); + + if (this.stencilStack.length === 0) { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + this.reverse = true; + this.count = 0; + } + + this.stencilStack.push(webGLData); + + var level = this.count; + + gl.colorMask(false, false, false, false); + + gl.stencilFunc(gl.ALWAYS,0,0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); + + // draw the triangle strip! + + if (webGLData.mode === 1) { + gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + + // draw a quad to increment.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + } + + this.reverse = !this.reverse; + } + else { + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + } + } + + gl.colorMask(true, true, true, true); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + + this.count++; +}; + +/** + * TODO this does not belong here! + * + * @param graphics {Graphics} + * @param webGLData {Array} + */ +WebGLStencilManager.prototype.bindGraphics = function (graphics, webGLData) { + //if (this._currentGraphics === graphics)return; + this._currentGraphics = graphics; + + var gl = this.renderer.gl; + + // bind the graphics object.. + var projection = this.renderer.projection, + offset = this.renderer.offset, + shader;// = this.renderer.shaderManager.primitiveShader; + + if (webGLData.mode === 1) { + shader = this.renderer.shaderManager.complexPrimitiveShader; + + this.renderer.shaderManager.setShader(shader); + + gl.uniform1f(shader.flipY, this.renderer.flipY); + + gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); + + gl.uniform2f(shader.projectionVector, projection.x, -projection.y); + gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); + + gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); + gl.uniform3fv(shader.color, webGLData.color); + + gl.uniform1f(shader.alpha, graphics.worldAlpha * webGLData.alpha); + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 2, 0); + + + // now do the rest.. + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + } + else { + //this.renderer.shaderManager.activatePrimitiveShader(); + shader = this.renderer.shaderManager.primitiveShader; + this.renderer.shaderManager.setShader( shader ); + + gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); + + gl.uniform1f(shader.flipY, this.renderer.flipY); + gl.uniform2f(shader.projectionVector, projection.x, -projection.y); + gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); + + gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); + + gl.uniform1f(shader.alpha, graphics.worldAlpha); + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); + gl.vertexAttribPointer(shader.colorAttribute, 4, gl.FLOAT, false,4 * 6, 2 * 4); + + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + } +}; + +/** + * @param graphics {Graphics} + * @param webGLData {Array} + */ +WebGLStencilManager.prototype.popStencil = function (graphics, webGLData) { + var gl = this.renderer.gl; + + this.stencilStack.pop(); + + this.count--; + + if (this.stencilStack.length === 0) { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + + } + else { + + var level = this.count; + + this.bindGraphics(graphics, webGLData, this.renderer); + + gl.colorMask(false, false, false, false); + + if (webGLData.mode === 1) { + this.reverse = !this.reverse; + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + + // draw a quad to increment.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + gl.stencilFunc(gl.ALWAYS,0,0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); + + // draw the triangle strip! + gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + } + + } + else { + // console.log("<<>>") + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + } + } + + gl.colorMask(true, true, true, true); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + + + } +}; + +/** + * Destroys the mask stack. + * + */ +WebGLStencilManager.prototype.destroy = function () { + this.renderer = null; + this.stencilStack = null; +}; diff --git a/src/core/renderers/webgl/utils/WebGLBlendModeManager.js b/src/core/renderers/webgl/utils/WebGLBlendModeManager.js deleted file mode 100644 index 9f2f89a..0000000 --- a/src/core/renderers/webgl/utils/WebGLBlendModeManager.js +++ /dev/null @@ -1,37 +0,0 @@ -var WebGLManager = require('./WebGLManager'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLBlendModeManager(renderer) { - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -WebGLBlendModeManager.prototype = Object.create(WebGLManager.prototype); -WebGLBlendModeManager.prototype.constructor = WebGLBlendModeManager; -module.exports = WebGLBlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as BlendModes.ADD - */ -WebGLBlendModeManager.prototype.setBlendMode = function (blendMode) { - if (this.currentBlendMode === blendMode) { - return false; - } - - this.currentBlendMode = blendMode; - - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); - - return true; -}; diff --git a/src/core/renderers/webgl/utils/WebGLFilterManager.js b/src/core/renderers/webgl/utils/WebGLFilterManager.js deleted file mode 100644 index 176e7cd..0000000 --- a/src/core/renderers/webgl/utils/WebGLFilterManager.js +++ /dev/null @@ -1,446 +0,0 @@ -var WebGLManager = require('./WebGLManager'), - FilterTexture = require('./FilterTexture'), - PixiShader = require('../shaders/PixiShader'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLFilterManager(renderer) { - WebGLManager.call(this, renderer); - - /** - * @member {any[]} - */ - this.filterStack = []; - - /** - * @member {any[]]} - */ - this.texturePool = []; - - /** - * @member {number} - */ - this.offsetX = 0; - - /** - * @member {number} - */ - this.offsetY = 0; - - // listen for context and update necessary buffers - var self = this; - this.renderer.on('context', function () { - self.texturePool.length = 0; - self.initShaderBuffers(); - }); -} - -WebGLFilterManager.prototype = Object.create(WebGLManager.prototype); -WebGLFilterManager.prototype.constructor = WebGLFilterManager; -module.exports = WebGLFilterManager; - -/** - * @param renderer {WebGLRenderer} - * @param buffer {ArrayBuffer} - */ -WebGLFilterManager.prototype.begin = function (renderer, buffer) { - this.renderer = renderer; - this.defaultShader = renderer.shaderManager.defaultShader; - - this.width = renderer.projection.x * 2; - this.height = -renderer.projection.y * 2; - - this.buffer = buffer; -}; - -/** - * Applies the filter and adds it to the current filter stack. - * - * @param filterBlock {object} the filter that will be pushed to the current filter stack - */ -WebGLFilterManager.prototype.pushFilter = function (filterBlock) { - var gl = this.renderer.gl; - - var projection = this.renderer.projection; - var offset = this.renderer.offset; - - filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds(); - - // filter program - // OPTIMISATION - the first filter is free if its a simple color change? - this.filterStack.push(filterBlock); - - var filter = filterBlock.filterPasses[0]; - - this.offsetX += filterBlock._filterArea.x; - this.offsetY += filterBlock._filterArea.y; - - var texture = this.texturePool.pop(); - if (!texture) { - texture = new FilterTexture(this.renderer.gl, this.width, this.height); - } - else { - texture.resize(this.width, this.height); - } - - gl.bindTexture(gl.TEXTURE_2D, texture.texture); - - var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea; - - var padding = filter.padding; - filterArea.x -= padding; - filterArea.y -= padding; - filterArea.width += padding * 2; - filterArea.height += padding * 2; - - // cap filter to screen size.. - if (filterArea.x < 0) { - filterArea.x = 0; - } - - if (filterArea.width > this.width) { - filterArea.width = this.width; - } - - if (filterArea.y < 0) { - filterArea.y = 0; - } - - if (filterArea.height > this.height) { - filterArea.height = this.height; - } - - //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); - - // set view port - gl.viewport(0, 0, filterArea.width, filterArea.height); - - projection.x = filterArea.width/2; - projection.y = -filterArea.height/2; - - offset.x = -filterArea.x; - offset.y = -filterArea.y; - - // update projection - // now restore the regular shader.. - // this.renderer.shaderManager.setShader(this.defaultShader); - //gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); - //gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y); - - gl.colorMask(true, true, true, true); - gl.clearColor(0,0,0, 0); - gl.clear(gl.COLOR_BUFFER_BIT); - - filterBlock._glFilterTexture = texture; - -}; - -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -WebGLFilterManager.prototype.popFilter = function () { - var gl = this.renderer.gl; - - var filterBlock = this.filterStack.pop(); - var filterArea = filterBlock._filterArea; - var texture = filterBlock._glFilterTexture; - var projection = this.renderer.projection; - var offset = this.renderer.offset; - - if (filterBlock.filterPasses.length > 1) { - gl.viewport(0, 0, filterArea.width, filterArea.height); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - - this.vertexArray[0] = 0; - this.vertexArray[1] = filterArea.height; - - this.vertexArray[2] = filterArea.width; - this.vertexArray[3] = filterArea.height; - - this.vertexArray[4] = 0; - this.vertexArray[5] = 0; - - this.vertexArray[6] = filterArea.width; - this.vertexArray[7] = 0; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - // now set the uvs.. - this.uvArray[2] = filterArea.width/this.width; - this.uvArray[5] = filterArea.height/this.height; - this.uvArray[6] = filterArea.width/this.width; - this.uvArray[7] = filterArea.height/this.height; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); - - var inputTexture = texture; - var outputTexture = this.texturePool.pop(); - if (!outputTexture) { - outputTexture = new FilterTexture(this.renderer.gl, this.width, this.height); - } - outputTexture.resize(this.width, this.height); - - // need to clear this FBO as it may have some left over elements from a previous filter. - gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); - gl.clear(gl.COLOR_BUFFER_BIT); - - gl.disable(gl.BLEND); - - for (var i = 0; i < filterBlock.filterPasses.length-1; i++) { - var filterPass = filterBlock.filterPasses[i]; - - gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); - - // set texture - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); - - // draw texture.. - //filterPass.applyFilterPass(filterArea.width, filterArea.height); - this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height); - - // swap the textures.. - var temp = inputTexture; - inputTexture = outputTexture; - outputTexture = temp; - } - - gl.enable(gl.BLEND); - - texture = inputTexture; - this.texturePool.push(outputTexture); - } - - var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1]; - - this.offsetX -= filterArea.x; - this.offsetY -= filterArea.y; - - var sizeX = this.width; - var sizeY = this.height; - - var offsetX = 0; - var offsetY = 0; - - var buffer = this.buffer; - - // time to render the filters texture to the previous scene - if (this.filterStack.length === 0) { - gl.colorMask(true, true, true, true);//this.transparent); - } - else { - var currentFilter = this.filterStack[this.filterStack.length-1]; - filterArea = currentFilter._filterArea; - - sizeX = filterArea.width; - sizeY = filterArea.height; - - offsetX = filterArea.x; - offsetY = filterArea.y; - - buffer = currentFilter._glFilterTexture.frameBuffer; - } - - // TODO need to remove these global elements.. - projection.x = sizeX/2; - projection.y = -sizeY/2; - - offset.x = offsetX; - offset.y = offsetY; - - filterArea = filterBlock._filterArea; - - var x = filterArea.x-offsetX; - var y = filterArea.y-offsetY; - - // update the buffers.. - // make sure to flip the y! - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - - this.vertexArray[0] = x; - this.vertexArray[1] = y + filterArea.height; - - this.vertexArray[2] = x + filterArea.width; - this.vertexArray[3] = y + filterArea.height; - - this.vertexArray[4] = x; - this.vertexArray[5] = y; - - this.vertexArray[6] = x + filterArea.width; - this.vertexArray[7] = y; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - - this.uvArray[2] = filterArea.width/this.width; - this.uvArray[5] = filterArea.height/this.height; - this.uvArray[6] = filterArea.width/this.width; - this.uvArray[7] = filterArea.height/this.height; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); - - gl.viewport(0, 0, sizeX, sizeY); - - // bind the buffer - gl.bindFramebuffer(gl.FRAMEBUFFER, buffer ); - - // set the blend mode! - //gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) - - // set texture - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, texture.texture); - - // apply! - this.applyFilterPass(filter, filterArea, sizeX, sizeY); - - // now restore the regular shader.. should happen automatically now.. - // this.renderer.shaderManager.setShader(this.defaultShader); - // gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2); - // gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY); - - // return the texture to the pool - this.texturePool.push(texture); - filterBlock._glFilterTexture = null; -}; - - -/** - * Applies the filter to the specified area. - * - * @param filter {AbstractFilter} the filter that needs to be applied - * @param filterArea {Texture} TODO - might need an update - * @param width {number} the horizontal range of the filter - * @param height {number} the vertical range of the filter - */ -WebGLFilterManager.prototype.applyFilterPass = function (filter, filterArea, width, height) { - // use program - var gl = this.renderer.gl; - - var shader = filter.shaders[gl.id]; - - if (!shader) { - shader = new PixiShader(gl); - - shader.fragmentSrc = filter.fragmentSrc; - shader.uniforms = filter.uniforms; - shader.init(); - - filter.shaders[gl.id] = shader; - } - - // set the shader - this.renderer.shaderManager.setShader(shader); - -// gl.useProgram(shader.program); - - gl.uniform2f(shader.projectionVector, width/2, -height/2); - gl.uniform2f(shader.offsetVector, 0,0); - - if (filter.uniforms.dimensions) { - filter.uniforms.dimensions.value[0] = this.width;//width; - filter.uniforms.dimensions.value[1] = this.height;//height; - filter.uniforms.dimensions.value[2] = this.vertexArray[0]; - filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; - } - - shader.syncUniforms(); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); - gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - - // draw the filter... - gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); - - this.renderer.drawCount++; -}; - -/** - * Initialises the shader buffers. - * - */ -WebGLFilterManager.prototype.initShaderBuffers = function () { - var gl = this.renderer.gl; - - // create some buffers - this.vertexBuffer = gl.createBuffer(); - this.uvBuffer = gl.createBuffer(); - this.colorBuffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - - // bind and upload the vertexs.. - // keep a reference to the vertexFloatData.. - this.vertexArray = new Float32Array([0.0, 0.0, - 1.0, 0.0, - 0.0, 1.0, - 1.0, 1.0]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW); - - // bind and upload the uv buffer - this.uvArray = new Float32Array([0.0, 0.0, - 1.0, 0.0, - 0.0, 1.0, - 1.0, 1.0]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.uvArray, gl.STATIC_DRAW); - - this.colorArray = new Float32Array([1.0, 0xFFFFFF, - 1.0, 0xFFFFFF, - 1.0, 0xFFFFFF, - 1.0, 0xFFFFFF]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.colorArray, gl.STATIC_DRAW); - - // bind and upload the index - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW); - -}; - -/** - * Destroys the filter and removes it from the filter stack. - * - */ -WebGLFilterManager.prototype.destroy = function () { - var gl = this.renderer.gl; - - this.filterStack = null; - - this.offsetX = 0; - this.offsetY = 0; - - // destroy textures - for (var i = 0; i < this.texturePool.length; i++) { - this.texturePool[i].destroy(); - } - - this.texturePool = null; - - //destroy buffers.. - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.uvBuffer); - gl.deleteBuffer(this.colorBuffer); - gl.deleteBuffer(this.indexBuffer); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/WebGLGraphics.js b/src/core/renderers/webgl/utils/WebGLGraphics.js index 09f540f..1d1fe40 100644 --- a/src/core/renderers/webgl/utils/WebGLGraphics.js +++ b/src/core/renderers/webgl/utils/WebGLGraphics.js @@ -1,6 +1,5 @@ var utils = require('../../../utils'), math = require('../../../math'), - Graphics = require('../../../../primitives/Graphics'), WebGLGraphicsData = require('./WebGLGraphicsData'); /** @@ -122,7 +121,7 @@ for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) { var data = graphics.graphicsData[i]; - if (data.type === Graphics.POLY) { + 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) { @@ -164,13 +163,13 @@ else { webGLData = WebGLGraphics.switchMode(webGL, 0); - if (data.type === Graphics.RECT) { + if (data.type === CONST.SHAPES.RECT) { WebGLGraphics.buildRectangle(data, webGLData); } - else if (data.type === Graphics.CIRC || data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) { WebGLGraphics.buildCircle(data, webGLData); } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { WebGLGraphics.buildRoundedRectangle(data, webGLData); } } @@ -415,7 +414,7 @@ var height; // TODO - bit hacky?? - if (graphicsData.type === Graphics.CIRC) { + if (graphicsData.type === CONST.SHAPES.CIRC) { width = circleData.radius; height = circleData.radius; } diff --git a/src/core/renderers/webgl/utils/WebGLManager.js b/src/core/renderers/webgl/utils/WebGLManager.js deleted file mode 100644 index 9d47b5c..0000000 --- a/src/core/renderers/webgl/utils/WebGLManager.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLManager(renderer) { - /** - * The renderer this manager works for. - * - * @member {WebGLRenderer} - */ - this.renderer = renderer; -} - -WebGLManager.prototype.constructor = WebGLManager; -module.exports = WebGLManager; - -WebGLManager.prototype.destroy = function () { - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/WebGLMaskManager.js b/src/core/renderers/webgl/utils/WebGLMaskManager.js deleted file mode 100644 index 6d46d85..0000000 --- a/src/core/renderers/webgl/utils/WebGLMaskManager.js +++ /dev/null @@ -1,41 +0,0 @@ -var WebGLManager = require('./WebGLManager'), - WebGLGraphics = require('./WebGLGraphics'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLMaskManager(renderer) { - WebGLManager.call(this, renderer); -} - -WebGLMaskManager.prototype = Object.create(WebGLManager.prototype); -WebGLMaskManager.prototype.constructor = WebGLMaskManager; -module.exports = WebGLMaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {any[]} - */ -WebGLMaskManager.prototype.pushMask = function (maskData) { - if (maskData.dirty) { - WebGLGraphics.updateGraphics(maskData, this.renderer.gl); - } - - if (!maskData._webGL[this.renderer.gl.id].data.length) { - return; - } - - this.renderer.stencilManager.pushStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); -}; - -/** - * Removes the last filter from the filter stack and doesn't return it. - * - * @param maskData {any[]} - */ -WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.stencilManager.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); -}; diff --git a/src/core/renderers/webgl/utils/WebGLShaderManager.js b/src/core/renderers/webgl/utils/WebGLShaderManager.js deleted file mode 100644 index d02d95d..0000000 --- a/src/core/renderers/webgl/utils/WebGLShaderManager.js +++ /dev/null @@ -1,172 +0,0 @@ -var WebGLManager = require('./WebGLManager'), - PrimitiveShader = require('../shaders/PrimitiveShader'), - ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), - PixiShader = require('../shaders/PixiShader'), - PixiFastShader = require('../shaders/PixiFastShader'), - StripShader = require('../shaders/StripShader'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLShaderManager(renderer) { - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.maxAttibs = 10; - - /** - * @member {any[]} - */ - this.attribState = []; - - /** - * @member {any[]} - */ - this.tempAttribState = []; - - for (var i = 0; i < this.maxAttibs; i++) { - this.attribState[i] = false; - } - - /** - * @member {any[]} - */ - this.stack = []; - - /** - * @member {number} - * @private - */ - this._currentId = -1; - - /** - * @member {Shader} - * @private - */ - 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; - - // listen for context and update necessary shaders - var self = this; - this.renderer.on('context', function (gl) { - // 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 PixiShader(gl); - - // this shader is used for the fast sprite rendering - self.fastShader = new PixiFastShader(gl); - - // the next one is used for rendering triangle strips - self.stripShader = new StripShader(gl); - - self.setShader(self.defaultShader); - }); -} - -WebGLShaderManager.prototype = Object.create(WebGLManager.prototype); -WebGLShaderManager.prototype.constructor = WebGLShaderManager; -module.exports = WebGLShaderManager; - -/** - * Takes the attributes given in parameters. - * - * @param attribs {Array} attribs - */ -WebGLShaderManager.prototype.setAttribs = function (attribs) { - // reset temp state - var i; - - for (i = 0; i < this.tempAttribState.length; i++) { - this.tempAttribState[i] = false; - } - - // set the new attribs - for (var a in attribs) { - this.tempAttribState[attribs[a]] = true; - } - - var gl = this.renderer.gl; - - for (i = 0; i < this.attribState.length; i++) { - if (this.attribState[i] !== this.tempAttribState[i]) { - this.attribState[i] = this.tempAttribState[i]; - - if (this.attribState[i]) { - gl.enableVertexAttribArray(i); - } - else { - gl.disableVertexAttribArray(i); - } - } - } -}; - -/** - * Sets the current shader. - * - * @param shader {Any} - */ -WebGLShaderManager.prototype.setShader = function (shader) { - if (this._currentId === shader.uuid) { - return false; - } - - this._currentId = shader.uuid; - - this.currentShader = shader; - - this.renderer.gl.useProgram(shader.program); - this.setAttribs(shader.attributes); - - return true; -}; - -/** - * Destroys this object. - * - */ -WebGLShaderManager.prototype.destroy = function () { - 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/utils/WebGLStencilManager.js b/src/core/renderers/webgl/utils/WebGLStencilManager.js deleted file mode 100644 index 0bedae6..0000000 --- a/src/core/renderers/webgl/utils/WebGLStencilManager.js +++ /dev/null @@ -1,256 +0,0 @@ -var WebGLManager = require('./WebGLManager'), - utils = require('../../../utils'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLStencilManager(renderer) { - WebGLManager.call(this, renderer); - - this.stencilStack = []; - this.reverse = true; - this.count = 0; -} - -WebGLStencilManager.prototype = Object.create(WebGLManager.prototype); -WebGLStencilManager.prototype.constructor = WebGLStencilManager; -module.exports = WebGLStencilManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param graphics {Graphics} - * @param webGLData {any[]} - */ -WebGLStencilManager.prototype.pushStencil = function (graphics, webGLData) { - var gl = this.renderer.gl; - - this.bindGraphics(graphics, webGLData, this.renderer); - - if (this.stencilStack.length === 0) { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - this.reverse = true; - this.count = 0; - } - - this.stencilStack.push(webGLData); - - var level = this.count; - - gl.colorMask(false, false, false, false); - - gl.stencilFunc(gl.ALWAYS,0,0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); - - // draw the triangle strip! - - if (webGLData.mode === 1) { - gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); - - if (this.reverse) { - gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); - } - else { - gl.stencilFunc(gl.EQUAL,level, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - } - - // draw a quad to increment.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - if (this.reverse) { - gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); - } - else { - gl.stencilFunc(gl.EQUAL,level+1, 0xFF); - } - - this.reverse = !this.reverse; - } - else { - if (!this.reverse) { - gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); - } - else { - gl.stencilFunc(gl.EQUAL,level, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - } - - gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); - - if (!this.reverse) { - gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); - } - else { - gl.stencilFunc(gl.EQUAL,level+1, 0xFF); - } - } - - gl.colorMask(true, true, true, true); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); - - this.count++; -}; - -/** - * TODO this does not belong here! - * - * @param graphics {Graphics} - * @param webGLData {Array} - */ -WebGLStencilManager.prototype.bindGraphics = function (graphics, webGLData) { - //if (this._currentGraphics === graphics)return; - this._currentGraphics = graphics; - - var gl = this.renderer.gl; - - // bind the graphics object.. - var projection = this.renderer.projection, - offset = this.renderer.offset, - shader;// = this.renderer.shaderManager.primitiveShader; - - if (webGLData.mode === 1) { - shader = this.renderer.shaderManager.complexPrimitiveShader; - - this.renderer.shaderManager.setShader(shader); - - gl.uniform1f(shader.flipY, this.renderer.flipY); - - gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); - - gl.uniform2f(shader.projectionVector, projection.x, -projection.y); - gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); - - gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); - gl.uniform3fv(shader.color, webGLData.color); - - gl.uniform1f(shader.alpha, graphics.worldAlpha * webGLData.alpha); - - gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); - - gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 2, 0); - - - // now do the rest.. - // set the index buffer! - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); - } - else { - //this.renderer.shaderManager.activatePrimitiveShader(); - shader = this.renderer.shaderManager.primitiveShader; - this.renderer.shaderManager.setShader( shader ); - - gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); - - gl.uniform1f(shader.flipY, this.renderer.flipY); - gl.uniform2f(shader.projectionVector, projection.x, -projection.y); - gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); - - gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); - - gl.uniform1f(shader.alpha, graphics.worldAlpha); - - gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); - - gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); - gl.vertexAttribPointer(shader.colorAttribute, 4, gl.FLOAT, false,4 * 6, 2 * 4); - - // set the index buffer! - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); - } -}; - -/** - * @param graphics {Graphics} - * @param webGLData {Array} - */ -WebGLStencilManager.prototype.popStencil = function (graphics, webGLData) { - var gl = this.renderer.gl; - - this.stencilStack.pop(); - - this.count--; - - if (this.stencilStack.length === 0) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - - } - else { - - var level = this.count; - - this.bindGraphics(graphics, webGLData, this.renderer); - - gl.colorMask(false, false, false, false); - - if (webGLData.mode === 1) { - this.reverse = !this.reverse; - - if (this.reverse) { - gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - } - else { - gl.stencilFunc(gl.EQUAL,level+1, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); - } - - // draw a quad to increment.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - gl.stencilFunc(gl.ALWAYS,0,0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); - - // draw the triangle strip! - gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); - - if (!this.reverse) { - gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); - } - else { - gl.stencilFunc(gl.EQUAL,level, 0xFF); - } - - } - else { - // console.log("<<>>") - if (!this.reverse) { - gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - } - else { - gl.stencilFunc(gl.EQUAL,level+1, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); - } - - gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); - - if (!this.reverse) { - gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); - } - else { - gl.stencilFunc(gl.EQUAL,level, 0xFF); - } - } - - gl.colorMask(true, true, true, true); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); - - - } -}; - -/** - * Destroys the mask stack. - * - */ -WebGLStencilManager.prototype.destroy = function () { - this.renderer = null; - this.stencilStack = null; -}; diff --git a/src/core/const.js b/src/core/const.js index 2803e5b..aea72ec 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -130,5 +130,25 @@ backgroundColor: 0x000000, clearBeforeRender: true, preserveDrawingBuffer: false + }, + + /** + * Constants that identify shapes, mainly to prevent `instanceof` calls. + * + * @static + * @constant + * @property {object} SHAPES + * @property {object} SHAPES.POLY=0 + * @property {object} SHAPES.RECT=1 + * @property {object} SHAPES.CIRC=2 + * @property {object} SHAPES.ELIP=3 + * @property {object} SHAPES.RREC=4 + */ + SHAPES: { + POLY = 0; + RECT = 1; + CIRC = 2; + ELIP = 3; + RREC = 4; } }; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index bfd59cf..7c0e7c6 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -95,7 +95,7 @@ this.filterArea = null; /** - * cached sin rotation and cos rotation + * cached sin rotation * * @member {number} * @private @@ -103,7 +103,7 @@ this._sr = 0; /** - * cached sin rotation and cos rotation + * cached cos rotation * * @member {number} * @private @@ -400,7 +400,7 @@ DisplayObject.prototype.generateTexture = function (resolution, scaleMode, renderer) { var bounds = this.getLocalBounds(); - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); _tempMatrix.tx = -bounds.x; _tempMatrix.ty = -bounds.y; @@ -474,7 +474,7 @@ var bounds = this.getLocalBounds(); if (!this._cachedSprite) { - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0); //, renderer); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0); this._cachedSprite = new Sprite(renderTexture); this._cachedSprite.worldTransform = this.worldTransform; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 3f6a3b6..69cfdc9 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Circle object can be used to specify a hit area for displayObjects @@ -29,10 +30,11 @@ this.radius = radius || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.CIRC in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.CIRC; } Circle.prototype.constructor = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index fc9df4d..4f2d066 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Ellipse object can be used to specify a hit area for displayObjects @@ -36,10 +37,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.ELIP in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.ELIP; } Ellipse.prototype.constructor = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index 5d4a464..ae89148 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -1,4 +1,5 @@ -var Point = require('../Point'); +var Point = require('../Point'), + CONST = require('../../const'); /** * @class @@ -33,6 +34,13 @@ * @member {Point[]} */ this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + */ + this.type = CONST.SHAPES.POLY; } Polygon.prototype.constructor = Polygon; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index c3454b5..16ac62a 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * the Rectangle object is an area defined by its position, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -34,10 +36,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RECT; } Rectangle.prototype.constructor = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0f2abfc..27d286e 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * The Rounded Rectangle object is an area defined by its position and has nice rounded corners, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -41,10 +43,11 @@ this.radius = radius || 20; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RRECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RREC; } RoundedRectangle.prototype.constructor = RoundedRectangle; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js new file mode 100644 index 0000000..42647c5 --- /dev/null +++ b/src/core/primitives/Graphics.js @@ -0,0 +1,1029 @@ +var core = require('../core'), + GraphicsData = require('./GraphicsData'); + +/** + * 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() { + core.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 {object[]} + * @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 = core.CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {object} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object[]} + * @private + */ + 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 core.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(core.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; + } + } + } +}); + +/** + * 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 core.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 core.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; + + 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 core.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 core.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 core.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 core.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 core.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 core.CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = core.Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + core.CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @method _renderWebGL + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderWebGL = function (renderSession) { + // 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 on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + core.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); + + return; + } + else { + renderSession.spriteBatch.stop(); + renderSession.blendModeManager.setBlendMode(this.blendMode); + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + if (this._filters) { + renderSession.filterManager.pushFilter(this._filterBlock); + } + + // check blend mode + if (this.blendMode !== renderSession.spriteBatch.currentBlendMode) { + renderSession.spriteBatch.currentBlendMode = this.blendMode; + + var blendModeWebGL = blendModesWebGL[renderSession.spriteBatch.currentBlendMode]; + + renderSession.spriteBatch.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); + } + + // check if the webgl graphic needs to be updated + if (this.glDirty) { + this.dirty = true; + this.glDirty = false; + } + + core.WebGLGraphics.renderGraphics(this, renderSession); + + // only render if it has children! + if (this.children.length) { + renderSession.spriteBatch.start(); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderWebGL(renderSession); + } + + renderSession.spriteBatch.stop(); + } + + if (this._filters) { + renderSession.filterManager.popFilter(); + } + + if (this._mask) { + renderSession.maskManager.popMask(this.mask, renderSession); + } + + renderSession.drawCount++; + + renderSession.spriteBatch.start(); + } +}; + +/** + * Renders the object using the Canvas renderer + * + * @method _renderCanvas + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderCanvas = function (renderSession) { + // 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; + + core.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession); + + return; + } + else { + var context = renderSession.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderSession.currentBlendMode) { + renderSession.currentBlendMode = this.blendMode; + context.globalCompositeOperation = blendModesCanvas[renderSession.currentBlendMode]; + } + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + var resolution = renderSession.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + core.CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderCanvas(renderSession); + } + + if (this._mask) { + renderSession.maskManager.popMask(renderSession); + } + } +}; + +/** + * 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 core.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 === core.CONST.SHAPES.RECT || type === core.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 === core.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 === core.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 core.CanvasBuffer(bounds.width, bounds.height); + var texture = core.Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new core.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.. + core.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 === core.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 new file mode 100644 index 0000000..9377ef7 --- /dev/null +++ b/src/core/primitives/GraphicsData.js @@ -0,0 +1,23 @@ +/** + * 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; diff --git a/src/core/primitives/index.js b/src/core/primitives/index.js new file mode 100644 index 0000000..7f02649 --- /dev/null +++ b/src/core/primitives/index.js @@ -0,0 +1,14 @@ +/** + * @file Main export of the PIXI primitives library + * @author Mat Groves + * @copyright 2013-2015 GoodBoyDigital + * @license {@link https://github.com/GoodBoyDigital/pixi.js/blob/master/LICENSE|MIT License} + */ + +/** + * @namespace PIXI + */ +module.exports = { + Graphics: require('./Graphics'), + GraphicsData: require('./GraphicsData') +}; diff --git a/src/core/renderers/canvas/CanvasGraphics.js b/src/core/renderers/canvas/CanvasGraphics.js index 2b1797a..ef7e765 100644 --- a/src/core/renderers/canvas/CanvasGraphics.js +++ b/src/core/renderers/canvas/CanvasGraphics.js @@ -1,4 +1,4 @@ -var Graphics = require('../../../primitives/Graphics'); +var CONST = require('../../const'); /** * A set of functions used by the canvas renderer to draw the primitive graphics data. @@ -30,7 +30,7 @@ context.lineWidth = data.lineWidth; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -61,7 +61,7 @@ context.stroke(); } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { if (data.fillColor || data.fillColor === 0) { context.globalAlpha = data.fillAlpha * worldAlpha; @@ -75,7 +75,7 @@ context.strokeRect(shape.x, shape.y, shape.width, shape.height); } } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); @@ -92,7 +92,7 @@ context.stroke(); } } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas var w = shape.width * 2; @@ -130,7 +130,7 @@ context.stroke(); } } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var rx = shape.x; var ry = shape.y; var width = shape.width; @@ -190,7 +190,7 @@ var data = graphics.graphicsData[i]; var shape = data.shape; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -207,18 +207,18 @@ } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { context.beginPath(); context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); context.closePath(); } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas @@ -245,7 +245,7 @@ context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); context.closePath(); } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var pts = shape.points; var rx = pts[0]; diff --git a/src/core/renderers/webgl/managers/WebGLBlendModeManager.js b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js new file mode 100644 index 0000000..9f2f89a --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js @@ -0,0 +1,37 @@ +var WebGLManager = require('./WebGLManager'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLBlendModeManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; +} + +WebGLBlendModeManager.prototype = Object.create(WebGLManager.prototype); +WebGLBlendModeManager.prototype.constructor = WebGLBlendModeManager; +module.exports = WebGLBlendModeManager; + +/** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as BlendModes.ADD + */ +WebGLBlendModeManager.prototype.setBlendMode = function (blendMode) { + if (this.currentBlendMode === blendMode) { + return false; + } + + this.currentBlendMode = blendMode; + + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; +}; diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js new file mode 100644 index 0000000..176e7cd --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -0,0 +1,446 @@ +var WebGLManager = require('./WebGLManager'), + FilterTexture = require('./FilterTexture'), + PixiShader = require('../shaders/PixiShader'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLFilterManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {any[]} + */ + this.filterStack = []; + + /** + * @member {any[]]} + */ + this.texturePool = []; + + /** + * @member {number} + */ + this.offsetX = 0; + + /** + * @member {number} + */ + this.offsetY = 0; + + // listen for context and update necessary buffers + var self = this; + this.renderer.on('context', function () { + self.texturePool.length = 0; + self.initShaderBuffers(); + }); +} + +WebGLFilterManager.prototype = Object.create(WebGLManager.prototype); +WebGLFilterManager.prototype.constructor = WebGLFilterManager; +module.exports = WebGLFilterManager; + +/** + * @param renderer {WebGLRenderer} + * @param buffer {ArrayBuffer} + */ +WebGLFilterManager.prototype.begin = function (renderer, buffer) { + this.renderer = renderer; + this.defaultShader = renderer.shaderManager.defaultShader; + + this.width = renderer.projection.x * 2; + this.height = -renderer.projection.y * 2; + + this.buffer = buffer; +}; + +/** + * Applies the filter and adds it to the current filter stack. + * + * @param filterBlock {object} the filter that will be pushed to the current filter stack + */ +WebGLFilterManager.prototype.pushFilter = function (filterBlock) { + var gl = this.renderer.gl; + + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds(); + + // filter program + // OPTIMISATION - the first filter is free if its a simple color change? + this.filterStack.push(filterBlock); + + var filter = filterBlock.filterPasses[0]; + + this.offsetX += filterBlock._filterArea.x; + this.offsetY += filterBlock._filterArea.y; + + var texture = this.texturePool.pop(); + if (!texture) { + texture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + else { + texture.resize(this.width, this.height); + } + + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea; + + var padding = filter.padding; + filterArea.x -= padding; + filterArea.y -= padding; + filterArea.width += padding * 2; + filterArea.height += padding * 2; + + // cap filter to screen size.. + if (filterArea.x < 0) { + filterArea.x = 0; + } + + if (filterArea.width > this.width) { + filterArea.width = this.width; + } + + if (filterArea.y < 0) { + filterArea.y = 0; + } + + if (filterArea.height > this.height) { + filterArea.height = this.height; + } + + //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); + + // set view port + gl.viewport(0, 0, filterArea.width, filterArea.height); + + projection.x = filterArea.width/2; + projection.y = -filterArea.height/2; + + offset.x = -filterArea.x; + offset.y = -filterArea.y; + + // update projection + // now restore the regular shader.. + // this.renderer.shaderManager.setShader(this.defaultShader); + //gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); + //gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y); + + gl.colorMask(true, true, true, true); + gl.clearColor(0,0,0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + + filterBlock._glFilterTexture = texture; + +}; + +/** + * Removes the last filter from the filter stack and doesn't return it. + * + */ +WebGLFilterManager.prototype.popFilter = function () { + var gl = this.renderer.gl; + + var filterBlock = this.filterStack.pop(); + var filterArea = filterBlock._filterArea; + var texture = filterBlock._glFilterTexture; + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + if (filterBlock.filterPasses.length > 1) { + gl.viewport(0, 0, filterArea.width, filterArea.height); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = 0; + this.vertexArray[1] = filterArea.height; + + this.vertexArray[2] = filterArea.width; + this.vertexArray[3] = filterArea.height; + + this.vertexArray[4] = 0; + this.vertexArray[5] = 0; + + this.vertexArray[6] = filterArea.width; + this.vertexArray[7] = 0; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + // now set the uvs.. + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + var inputTexture = texture; + var outputTexture = this.texturePool.pop(); + if (!outputTexture) { + outputTexture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + outputTexture.resize(this.width, this.height); + + // need to clear this FBO as it may have some left over elements from a previous filter. + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + gl.clear(gl.COLOR_BUFFER_BIT); + + gl.disable(gl.BLEND); + + for (var i = 0; i < filterBlock.filterPasses.length-1; i++) { + var filterPass = filterBlock.filterPasses[i]; + + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); + + // draw texture.. + //filterPass.applyFilterPass(filterArea.width, filterArea.height); + this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height); + + // swap the textures.. + var temp = inputTexture; + inputTexture = outputTexture; + outputTexture = temp; + } + + gl.enable(gl.BLEND); + + texture = inputTexture; + this.texturePool.push(outputTexture); + } + + var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1]; + + this.offsetX -= filterArea.x; + this.offsetY -= filterArea.y; + + var sizeX = this.width; + var sizeY = this.height; + + var offsetX = 0; + var offsetY = 0; + + var buffer = this.buffer; + + // time to render the filters texture to the previous scene + if (this.filterStack.length === 0) { + gl.colorMask(true, true, true, true);//this.transparent); + } + else { + var currentFilter = this.filterStack[this.filterStack.length-1]; + filterArea = currentFilter._filterArea; + + sizeX = filterArea.width; + sizeY = filterArea.height; + + offsetX = filterArea.x; + offsetY = filterArea.y; + + buffer = currentFilter._glFilterTexture.frameBuffer; + } + + // TODO need to remove these global elements.. + projection.x = sizeX/2; + projection.y = -sizeY/2; + + offset.x = offsetX; + offset.y = offsetY; + + filterArea = filterBlock._filterArea; + + var x = filterArea.x-offsetX; + var y = filterArea.y-offsetY; + + // update the buffers.. + // make sure to flip the y! + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = x; + this.vertexArray[1] = y + filterArea.height; + + this.vertexArray[2] = x + filterArea.width; + this.vertexArray[3] = y + filterArea.height; + + this.vertexArray[4] = x; + this.vertexArray[5] = y; + + this.vertexArray[6] = x + filterArea.width; + this.vertexArray[7] = y; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + gl.viewport(0, 0, sizeX, sizeY); + + // bind the buffer + gl.bindFramebuffer(gl.FRAMEBUFFER, buffer ); + + // set the blend mode! + //gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + // apply! + this.applyFilterPass(filter, filterArea, sizeX, sizeY); + + // now restore the regular shader.. should happen automatically now.. + // this.renderer.shaderManager.setShader(this.defaultShader); + // gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2); + // gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY); + + // return the texture to the pool + this.texturePool.push(texture); + filterBlock._glFilterTexture = null; +}; + + +/** + * Applies the filter to the specified area. + * + * @param filter {AbstractFilter} the filter that needs to be applied + * @param filterArea {Texture} TODO - might need an update + * @param width {number} the horizontal range of the filter + * @param height {number} the vertical range of the filter + */ +WebGLFilterManager.prototype.applyFilterPass = function (filter, filterArea, width, height) { + // use program + var gl = this.renderer.gl; + + var shader = filter.shaders[gl.id]; + + if (!shader) { + shader = new PixiShader(gl); + + shader.fragmentSrc = filter.fragmentSrc; + shader.uniforms = filter.uniforms; + shader.init(); + + filter.shaders[gl.id] = shader; + } + + // set the shader + this.renderer.shaderManager.setShader(shader); + +// gl.useProgram(shader.program); + + gl.uniform2f(shader.projectionVector, width/2, -height/2); + gl.uniform2f(shader.offsetVector, 0,0); + + if (filter.uniforms.dimensions) { + filter.uniforms.dimensions.value[0] = this.width;//width; + filter.uniforms.dimensions.value[1] = this.height;//height; + filter.uniforms.dimensions.value[2] = this.vertexArray[0]; + filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; + } + + shader.syncUniforms(); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + + // draw the filter... + gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); + + this.renderer.drawCount++; +}; + +/** + * Initialises the shader buffers. + * + */ +WebGLFilterManager.prototype.initShaderBuffers = function () { + var gl = this.renderer.gl; + + // create some buffers + this.vertexBuffer = gl.createBuffer(); + this.uvBuffer = gl.createBuffer(); + this.colorBuffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + + // bind and upload the vertexs.. + // keep a reference to the vertexFloatData.. + this.vertexArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW); + + // bind and upload the uv buffer + this.uvArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.uvArray, gl.STATIC_DRAW); + + this.colorArray = new Float32Array([1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.colorArray, gl.STATIC_DRAW); + + // bind and upload the index + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW); + +}; + +/** + * Destroys the filter and removes it from the filter stack. + * + */ +WebGLFilterManager.prototype.destroy = function () { + var gl = this.renderer.gl; + + this.filterStack = null; + + this.offsetX = 0; + this.offsetY = 0; + + // destroy textures + for (var i = 0; i < this.texturePool.length; i++) { + this.texturePool[i].destroy(); + } + + this.texturePool = null; + + //destroy buffers.. + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.uvBuffer); + gl.deleteBuffer(this.colorBuffer); + gl.deleteBuffer(this.indexBuffer); + + this.renderer = null; +}; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js new file mode 100644 index 0000000..9d47b5c --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -0,0 +1,20 @@ +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLManager(renderer) { + /** + * The renderer this manager works for. + * + * @member {WebGLRenderer} + */ + this.renderer = renderer; +} + +WebGLManager.prototype.constructor = WebGLManager; +module.exports = WebGLManager; + +WebGLManager.prototype.destroy = function () { + this.renderer = null; +}; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js new file mode 100644 index 0000000..6d46d85 --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -0,0 +1,41 @@ +var WebGLManager = require('./WebGLManager'), + WebGLGraphics = require('./WebGLGraphics'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLMaskManager(renderer) { + WebGLManager.call(this, renderer); +} + +WebGLMaskManager.prototype = Object.create(WebGLManager.prototype); +WebGLMaskManager.prototype.constructor = WebGLMaskManager; +module.exports = WebGLMaskManager; + +/** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {any[]} + */ +WebGLMaskManager.prototype.pushMask = function (maskData) { + if (maskData.dirty) { + WebGLGraphics.updateGraphics(maskData, this.renderer.gl); + } + + if (!maskData._webGL[this.renderer.gl.id].data.length) { + return; + } + + this.renderer.stencilManager.pushStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); +}; + +/** + * Removes the last filter from the filter stack and doesn't return it. + * + * @param maskData {any[]} + */ +WebGLMaskManager.prototype.popMask = function (maskData) { + this.renderer.stencilManager.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 new file mode 100644 index 0000000..d02d95d --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -0,0 +1,172 @@ +var WebGLManager = require('./WebGLManager'), + PrimitiveShader = require('../shaders/PrimitiveShader'), + ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), + PixiShader = require('../shaders/PixiShader'), + PixiFastShader = require('../shaders/PixiFastShader'), + StripShader = require('../shaders/StripShader'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLShaderManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {number} + */ + this.maxAttibs = 10; + + /** + * @member {any[]} + */ + this.attribState = []; + + /** + * @member {any[]} + */ + this.tempAttribState = []; + + for (var i = 0; i < this.maxAttibs; i++) { + this.attribState[i] = false; + } + + /** + * @member {any[]} + */ + this.stack = []; + + /** + * @member {number} + * @private + */ + this._currentId = -1; + + /** + * @member {Shader} + * @private + */ + 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; + + // listen for context and update necessary shaders + var self = this; + this.renderer.on('context', function (gl) { + // 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 PixiShader(gl); + + // this shader is used for the fast sprite rendering + self.fastShader = new PixiFastShader(gl); + + // the next one is used for rendering triangle strips + self.stripShader = new StripShader(gl); + + self.setShader(self.defaultShader); + }); +} + +WebGLShaderManager.prototype = Object.create(WebGLManager.prototype); +WebGLShaderManager.prototype.constructor = WebGLShaderManager; +module.exports = WebGLShaderManager; + +/** + * Takes the attributes given in parameters. + * + * @param attribs {Array} attribs + */ +WebGLShaderManager.prototype.setAttribs = function (attribs) { + // reset temp state + var i; + + for (i = 0; i < this.tempAttribState.length; i++) { + this.tempAttribState[i] = false; + } + + // set the new attribs + for (var a in attribs) { + this.tempAttribState[attribs[a]] = true; + } + + var gl = this.renderer.gl; + + for (i = 0; i < this.attribState.length; i++) { + if (this.attribState[i] !== this.tempAttribState[i]) { + this.attribState[i] = this.tempAttribState[i]; + + if (this.attribState[i]) { + gl.enableVertexAttribArray(i); + } + else { + gl.disableVertexAttribArray(i); + } + } + } +}; + +/** + * Sets the current shader. + * + * @param shader {Any} + */ +WebGLShaderManager.prototype.setShader = function (shader) { + if (this._currentId === shader.uuid) { + return false; + } + + this._currentId = shader.uuid; + + this.currentShader = shader; + + this.renderer.gl.useProgram(shader.program); + this.setAttribs(shader.attributes); + + return true; +}; + +/** + * Destroys this object. + * + */ +WebGLShaderManager.prototype.destroy = function () { + 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/managers/WebGLStencilManager.js b/src/core/renderers/webgl/managers/WebGLStencilManager.js new file mode 100644 index 0000000..0bedae6 --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLStencilManager.js @@ -0,0 +1,256 @@ +var WebGLManager = require('./WebGLManager'), + utils = require('../../../utils'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLStencilManager(renderer) { + WebGLManager.call(this, renderer); + + this.stencilStack = []; + this.reverse = true; + this.count = 0; +} + +WebGLStencilManager.prototype = Object.create(WebGLManager.prototype); +WebGLStencilManager.prototype.constructor = WebGLStencilManager; +module.exports = WebGLStencilManager; + +/** + * Applies the Mask and adds it to the current filter stack. + * + * @param graphics {Graphics} + * @param webGLData {any[]} + */ +WebGLStencilManager.prototype.pushStencil = function (graphics, webGLData) { + var gl = this.renderer.gl; + + this.bindGraphics(graphics, webGLData, this.renderer); + + if (this.stencilStack.length === 0) { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + this.reverse = true; + this.count = 0; + } + + this.stencilStack.push(webGLData); + + var level = this.count; + + gl.colorMask(false, false, false, false); + + gl.stencilFunc(gl.ALWAYS,0,0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); + + // draw the triangle strip! + + if (webGLData.mode === 1) { + gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + + // draw a quad to increment.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + } + + this.reverse = !this.reverse; + } + else { + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + } + } + + gl.colorMask(true, true, true, true); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + + this.count++; +}; + +/** + * TODO this does not belong here! + * + * @param graphics {Graphics} + * @param webGLData {Array} + */ +WebGLStencilManager.prototype.bindGraphics = function (graphics, webGLData) { + //if (this._currentGraphics === graphics)return; + this._currentGraphics = graphics; + + var gl = this.renderer.gl; + + // bind the graphics object.. + var projection = this.renderer.projection, + offset = this.renderer.offset, + shader;// = this.renderer.shaderManager.primitiveShader; + + if (webGLData.mode === 1) { + shader = this.renderer.shaderManager.complexPrimitiveShader; + + this.renderer.shaderManager.setShader(shader); + + gl.uniform1f(shader.flipY, this.renderer.flipY); + + gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); + + gl.uniform2f(shader.projectionVector, projection.x, -projection.y); + gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); + + gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); + gl.uniform3fv(shader.color, webGLData.color); + + gl.uniform1f(shader.alpha, graphics.worldAlpha * webGLData.alpha); + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 2, 0); + + + // now do the rest.. + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + } + else { + //this.renderer.shaderManager.activatePrimitiveShader(); + shader = this.renderer.shaderManager.primitiveShader; + this.renderer.shaderManager.setShader( shader ); + + gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); + + gl.uniform1f(shader.flipY, this.renderer.flipY); + gl.uniform2f(shader.projectionVector, projection.x, -projection.y); + gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); + + gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); + + gl.uniform1f(shader.alpha, graphics.worldAlpha); + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); + gl.vertexAttribPointer(shader.colorAttribute, 4, gl.FLOAT, false,4 * 6, 2 * 4); + + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + } +}; + +/** + * @param graphics {Graphics} + * @param webGLData {Array} + */ +WebGLStencilManager.prototype.popStencil = function (graphics, webGLData) { + var gl = this.renderer.gl; + + this.stencilStack.pop(); + + this.count--; + + if (this.stencilStack.length === 0) { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + + } + else { + + var level = this.count; + + this.bindGraphics(graphics, webGLData, this.renderer); + + gl.colorMask(false, false, false, false); + + if (webGLData.mode === 1) { + this.reverse = !this.reverse; + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + + // draw a quad to increment.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + gl.stencilFunc(gl.ALWAYS,0,0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); + + // draw the triangle strip! + gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + } + + } + else { + // console.log("<<>>") + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + } + } + + gl.colorMask(true, true, true, true); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + + + } +}; + +/** + * Destroys the mask stack. + * + */ +WebGLStencilManager.prototype.destroy = function () { + this.renderer = null; + this.stencilStack = null; +}; diff --git a/src/core/renderers/webgl/utils/WebGLBlendModeManager.js b/src/core/renderers/webgl/utils/WebGLBlendModeManager.js deleted file mode 100644 index 9f2f89a..0000000 --- a/src/core/renderers/webgl/utils/WebGLBlendModeManager.js +++ /dev/null @@ -1,37 +0,0 @@ -var WebGLManager = require('./WebGLManager'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLBlendModeManager(renderer) { - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -WebGLBlendModeManager.prototype = Object.create(WebGLManager.prototype); -WebGLBlendModeManager.prototype.constructor = WebGLBlendModeManager; -module.exports = WebGLBlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as BlendModes.ADD - */ -WebGLBlendModeManager.prototype.setBlendMode = function (blendMode) { - if (this.currentBlendMode === blendMode) { - return false; - } - - this.currentBlendMode = blendMode; - - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); - - return true; -}; diff --git a/src/core/renderers/webgl/utils/WebGLFilterManager.js b/src/core/renderers/webgl/utils/WebGLFilterManager.js deleted file mode 100644 index 176e7cd..0000000 --- a/src/core/renderers/webgl/utils/WebGLFilterManager.js +++ /dev/null @@ -1,446 +0,0 @@ -var WebGLManager = require('./WebGLManager'), - FilterTexture = require('./FilterTexture'), - PixiShader = require('../shaders/PixiShader'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLFilterManager(renderer) { - WebGLManager.call(this, renderer); - - /** - * @member {any[]} - */ - this.filterStack = []; - - /** - * @member {any[]]} - */ - this.texturePool = []; - - /** - * @member {number} - */ - this.offsetX = 0; - - /** - * @member {number} - */ - this.offsetY = 0; - - // listen for context and update necessary buffers - var self = this; - this.renderer.on('context', function () { - self.texturePool.length = 0; - self.initShaderBuffers(); - }); -} - -WebGLFilterManager.prototype = Object.create(WebGLManager.prototype); -WebGLFilterManager.prototype.constructor = WebGLFilterManager; -module.exports = WebGLFilterManager; - -/** - * @param renderer {WebGLRenderer} - * @param buffer {ArrayBuffer} - */ -WebGLFilterManager.prototype.begin = function (renderer, buffer) { - this.renderer = renderer; - this.defaultShader = renderer.shaderManager.defaultShader; - - this.width = renderer.projection.x * 2; - this.height = -renderer.projection.y * 2; - - this.buffer = buffer; -}; - -/** - * Applies the filter and adds it to the current filter stack. - * - * @param filterBlock {object} the filter that will be pushed to the current filter stack - */ -WebGLFilterManager.prototype.pushFilter = function (filterBlock) { - var gl = this.renderer.gl; - - var projection = this.renderer.projection; - var offset = this.renderer.offset; - - filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds(); - - // filter program - // OPTIMISATION - the first filter is free if its a simple color change? - this.filterStack.push(filterBlock); - - var filter = filterBlock.filterPasses[0]; - - this.offsetX += filterBlock._filterArea.x; - this.offsetY += filterBlock._filterArea.y; - - var texture = this.texturePool.pop(); - if (!texture) { - texture = new FilterTexture(this.renderer.gl, this.width, this.height); - } - else { - texture.resize(this.width, this.height); - } - - gl.bindTexture(gl.TEXTURE_2D, texture.texture); - - var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea; - - var padding = filter.padding; - filterArea.x -= padding; - filterArea.y -= padding; - filterArea.width += padding * 2; - filterArea.height += padding * 2; - - // cap filter to screen size.. - if (filterArea.x < 0) { - filterArea.x = 0; - } - - if (filterArea.width > this.width) { - filterArea.width = this.width; - } - - if (filterArea.y < 0) { - filterArea.y = 0; - } - - if (filterArea.height > this.height) { - filterArea.height = this.height; - } - - //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); - - // set view port - gl.viewport(0, 0, filterArea.width, filterArea.height); - - projection.x = filterArea.width/2; - projection.y = -filterArea.height/2; - - offset.x = -filterArea.x; - offset.y = -filterArea.y; - - // update projection - // now restore the regular shader.. - // this.renderer.shaderManager.setShader(this.defaultShader); - //gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); - //gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y); - - gl.colorMask(true, true, true, true); - gl.clearColor(0,0,0, 0); - gl.clear(gl.COLOR_BUFFER_BIT); - - filterBlock._glFilterTexture = texture; - -}; - -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -WebGLFilterManager.prototype.popFilter = function () { - var gl = this.renderer.gl; - - var filterBlock = this.filterStack.pop(); - var filterArea = filterBlock._filterArea; - var texture = filterBlock._glFilterTexture; - var projection = this.renderer.projection; - var offset = this.renderer.offset; - - if (filterBlock.filterPasses.length > 1) { - gl.viewport(0, 0, filterArea.width, filterArea.height); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - - this.vertexArray[0] = 0; - this.vertexArray[1] = filterArea.height; - - this.vertexArray[2] = filterArea.width; - this.vertexArray[3] = filterArea.height; - - this.vertexArray[4] = 0; - this.vertexArray[5] = 0; - - this.vertexArray[6] = filterArea.width; - this.vertexArray[7] = 0; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - // now set the uvs.. - this.uvArray[2] = filterArea.width/this.width; - this.uvArray[5] = filterArea.height/this.height; - this.uvArray[6] = filterArea.width/this.width; - this.uvArray[7] = filterArea.height/this.height; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); - - var inputTexture = texture; - var outputTexture = this.texturePool.pop(); - if (!outputTexture) { - outputTexture = new FilterTexture(this.renderer.gl, this.width, this.height); - } - outputTexture.resize(this.width, this.height); - - // need to clear this FBO as it may have some left over elements from a previous filter. - gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); - gl.clear(gl.COLOR_BUFFER_BIT); - - gl.disable(gl.BLEND); - - for (var i = 0; i < filterBlock.filterPasses.length-1; i++) { - var filterPass = filterBlock.filterPasses[i]; - - gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); - - // set texture - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); - - // draw texture.. - //filterPass.applyFilterPass(filterArea.width, filterArea.height); - this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height); - - // swap the textures.. - var temp = inputTexture; - inputTexture = outputTexture; - outputTexture = temp; - } - - gl.enable(gl.BLEND); - - texture = inputTexture; - this.texturePool.push(outputTexture); - } - - var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1]; - - this.offsetX -= filterArea.x; - this.offsetY -= filterArea.y; - - var sizeX = this.width; - var sizeY = this.height; - - var offsetX = 0; - var offsetY = 0; - - var buffer = this.buffer; - - // time to render the filters texture to the previous scene - if (this.filterStack.length === 0) { - gl.colorMask(true, true, true, true);//this.transparent); - } - else { - var currentFilter = this.filterStack[this.filterStack.length-1]; - filterArea = currentFilter._filterArea; - - sizeX = filterArea.width; - sizeY = filterArea.height; - - offsetX = filterArea.x; - offsetY = filterArea.y; - - buffer = currentFilter._glFilterTexture.frameBuffer; - } - - // TODO need to remove these global elements.. - projection.x = sizeX/2; - projection.y = -sizeY/2; - - offset.x = offsetX; - offset.y = offsetY; - - filterArea = filterBlock._filterArea; - - var x = filterArea.x-offsetX; - var y = filterArea.y-offsetY; - - // update the buffers.. - // make sure to flip the y! - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - - this.vertexArray[0] = x; - this.vertexArray[1] = y + filterArea.height; - - this.vertexArray[2] = x + filterArea.width; - this.vertexArray[3] = y + filterArea.height; - - this.vertexArray[4] = x; - this.vertexArray[5] = y; - - this.vertexArray[6] = x + filterArea.width; - this.vertexArray[7] = y; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - - this.uvArray[2] = filterArea.width/this.width; - this.uvArray[5] = filterArea.height/this.height; - this.uvArray[6] = filterArea.width/this.width; - this.uvArray[7] = filterArea.height/this.height; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); - - gl.viewport(0, 0, sizeX, sizeY); - - // bind the buffer - gl.bindFramebuffer(gl.FRAMEBUFFER, buffer ); - - // set the blend mode! - //gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) - - // set texture - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, texture.texture); - - // apply! - this.applyFilterPass(filter, filterArea, sizeX, sizeY); - - // now restore the regular shader.. should happen automatically now.. - // this.renderer.shaderManager.setShader(this.defaultShader); - // gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2); - // gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY); - - // return the texture to the pool - this.texturePool.push(texture); - filterBlock._glFilterTexture = null; -}; - - -/** - * Applies the filter to the specified area. - * - * @param filter {AbstractFilter} the filter that needs to be applied - * @param filterArea {Texture} TODO - might need an update - * @param width {number} the horizontal range of the filter - * @param height {number} the vertical range of the filter - */ -WebGLFilterManager.prototype.applyFilterPass = function (filter, filterArea, width, height) { - // use program - var gl = this.renderer.gl; - - var shader = filter.shaders[gl.id]; - - if (!shader) { - shader = new PixiShader(gl); - - shader.fragmentSrc = filter.fragmentSrc; - shader.uniforms = filter.uniforms; - shader.init(); - - filter.shaders[gl.id] = shader; - } - - // set the shader - this.renderer.shaderManager.setShader(shader); - -// gl.useProgram(shader.program); - - gl.uniform2f(shader.projectionVector, width/2, -height/2); - gl.uniform2f(shader.offsetVector, 0,0); - - if (filter.uniforms.dimensions) { - filter.uniforms.dimensions.value[0] = this.width;//width; - filter.uniforms.dimensions.value[1] = this.height;//height; - filter.uniforms.dimensions.value[2] = this.vertexArray[0]; - filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; - } - - shader.syncUniforms(); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); - gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - - // draw the filter... - gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); - - this.renderer.drawCount++; -}; - -/** - * Initialises the shader buffers. - * - */ -WebGLFilterManager.prototype.initShaderBuffers = function () { - var gl = this.renderer.gl; - - // create some buffers - this.vertexBuffer = gl.createBuffer(); - this.uvBuffer = gl.createBuffer(); - this.colorBuffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - - // bind and upload the vertexs.. - // keep a reference to the vertexFloatData.. - this.vertexArray = new Float32Array([0.0, 0.0, - 1.0, 0.0, - 0.0, 1.0, - 1.0, 1.0]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW); - - // bind and upload the uv buffer - this.uvArray = new Float32Array([0.0, 0.0, - 1.0, 0.0, - 0.0, 1.0, - 1.0, 1.0]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.uvArray, gl.STATIC_DRAW); - - this.colorArray = new Float32Array([1.0, 0xFFFFFF, - 1.0, 0xFFFFFF, - 1.0, 0xFFFFFF, - 1.0, 0xFFFFFF]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.colorArray, gl.STATIC_DRAW); - - // bind and upload the index - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW); - -}; - -/** - * Destroys the filter and removes it from the filter stack. - * - */ -WebGLFilterManager.prototype.destroy = function () { - var gl = this.renderer.gl; - - this.filterStack = null; - - this.offsetX = 0; - this.offsetY = 0; - - // destroy textures - for (var i = 0; i < this.texturePool.length; i++) { - this.texturePool[i].destroy(); - } - - this.texturePool = null; - - //destroy buffers.. - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.uvBuffer); - gl.deleteBuffer(this.colorBuffer); - gl.deleteBuffer(this.indexBuffer); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/WebGLGraphics.js b/src/core/renderers/webgl/utils/WebGLGraphics.js index 09f540f..1d1fe40 100644 --- a/src/core/renderers/webgl/utils/WebGLGraphics.js +++ b/src/core/renderers/webgl/utils/WebGLGraphics.js @@ -1,6 +1,5 @@ var utils = require('../../../utils'), math = require('../../../math'), - Graphics = require('../../../../primitives/Graphics'), WebGLGraphicsData = require('./WebGLGraphicsData'); /** @@ -122,7 +121,7 @@ for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) { var data = graphics.graphicsData[i]; - if (data.type === Graphics.POLY) { + 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) { @@ -164,13 +163,13 @@ else { webGLData = WebGLGraphics.switchMode(webGL, 0); - if (data.type === Graphics.RECT) { + if (data.type === CONST.SHAPES.RECT) { WebGLGraphics.buildRectangle(data, webGLData); } - else if (data.type === Graphics.CIRC || data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) { WebGLGraphics.buildCircle(data, webGLData); } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { WebGLGraphics.buildRoundedRectangle(data, webGLData); } } @@ -415,7 +414,7 @@ var height; // TODO - bit hacky?? - if (graphicsData.type === Graphics.CIRC) { + if (graphicsData.type === CONST.SHAPES.CIRC) { width = circleData.radius; height = circleData.radius; } diff --git a/src/core/renderers/webgl/utils/WebGLManager.js b/src/core/renderers/webgl/utils/WebGLManager.js deleted file mode 100644 index 9d47b5c..0000000 --- a/src/core/renderers/webgl/utils/WebGLManager.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLManager(renderer) { - /** - * The renderer this manager works for. - * - * @member {WebGLRenderer} - */ - this.renderer = renderer; -} - -WebGLManager.prototype.constructor = WebGLManager; -module.exports = WebGLManager; - -WebGLManager.prototype.destroy = function () { - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/WebGLMaskManager.js b/src/core/renderers/webgl/utils/WebGLMaskManager.js deleted file mode 100644 index 6d46d85..0000000 --- a/src/core/renderers/webgl/utils/WebGLMaskManager.js +++ /dev/null @@ -1,41 +0,0 @@ -var WebGLManager = require('./WebGLManager'), - WebGLGraphics = require('./WebGLGraphics'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLMaskManager(renderer) { - WebGLManager.call(this, renderer); -} - -WebGLMaskManager.prototype = Object.create(WebGLManager.prototype); -WebGLMaskManager.prototype.constructor = WebGLMaskManager; -module.exports = WebGLMaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {any[]} - */ -WebGLMaskManager.prototype.pushMask = function (maskData) { - if (maskData.dirty) { - WebGLGraphics.updateGraphics(maskData, this.renderer.gl); - } - - if (!maskData._webGL[this.renderer.gl.id].data.length) { - return; - } - - this.renderer.stencilManager.pushStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); -}; - -/** - * Removes the last filter from the filter stack and doesn't return it. - * - * @param maskData {any[]} - */ -WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.stencilManager.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); -}; diff --git a/src/core/renderers/webgl/utils/WebGLShaderManager.js b/src/core/renderers/webgl/utils/WebGLShaderManager.js deleted file mode 100644 index d02d95d..0000000 --- a/src/core/renderers/webgl/utils/WebGLShaderManager.js +++ /dev/null @@ -1,172 +0,0 @@ -var WebGLManager = require('./WebGLManager'), - PrimitiveShader = require('../shaders/PrimitiveShader'), - ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), - PixiShader = require('../shaders/PixiShader'), - PixiFastShader = require('../shaders/PixiFastShader'), - StripShader = require('../shaders/StripShader'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLShaderManager(renderer) { - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.maxAttibs = 10; - - /** - * @member {any[]} - */ - this.attribState = []; - - /** - * @member {any[]} - */ - this.tempAttribState = []; - - for (var i = 0; i < this.maxAttibs; i++) { - this.attribState[i] = false; - } - - /** - * @member {any[]} - */ - this.stack = []; - - /** - * @member {number} - * @private - */ - this._currentId = -1; - - /** - * @member {Shader} - * @private - */ - 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; - - // listen for context and update necessary shaders - var self = this; - this.renderer.on('context', function (gl) { - // 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 PixiShader(gl); - - // this shader is used for the fast sprite rendering - self.fastShader = new PixiFastShader(gl); - - // the next one is used for rendering triangle strips - self.stripShader = new StripShader(gl); - - self.setShader(self.defaultShader); - }); -} - -WebGLShaderManager.prototype = Object.create(WebGLManager.prototype); -WebGLShaderManager.prototype.constructor = WebGLShaderManager; -module.exports = WebGLShaderManager; - -/** - * Takes the attributes given in parameters. - * - * @param attribs {Array} attribs - */ -WebGLShaderManager.prototype.setAttribs = function (attribs) { - // reset temp state - var i; - - for (i = 0; i < this.tempAttribState.length; i++) { - this.tempAttribState[i] = false; - } - - // set the new attribs - for (var a in attribs) { - this.tempAttribState[attribs[a]] = true; - } - - var gl = this.renderer.gl; - - for (i = 0; i < this.attribState.length; i++) { - if (this.attribState[i] !== this.tempAttribState[i]) { - this.attribState[i] = this.tempAttribState[i]; - - if (this.attribState[i]) { - gl.enableVertexAttribArray(i); - } - else { - gl.disableVertexAttribArray(i); - } - } - } -}; - -/** - * Sets the current shader. - * - * @param shader {Any} - */ -WebGLShaderManager.prototype.setShader = function (shader) { - if (this._currentId === shader.uuid) { - return false; - } - - this._currentId = shader.uuid; - - this.currentShader = shader; - - this.renderer.gl.useProgram(shader.program); - this.setAttribs(shader.attributes); - - return true; -}; - -/** - * Destroys this object. - * - */ -WebGLShaderManager.prototype.destroy = function () { - 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/utils/WebGLStencilManager.js b/src/core/renderers/webgl/utils/WebGLStencilManager.js deleted file mode 100644 index 0bedae6..0000000 --- a/src/core/renderers/webgl/utils/WebGLStencilManager.js +++ /dev/null @@ -1,256 +0,0 @@ -var WebGLManager = require('./WebGLManager'), - utils = require('../../../utils'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLStencilManager(renderer) { - WebGLManager.call(this, renderer); - - this.stencilStack = []; - this.reverse = true; - this.count = 0; -} - -WebGLStencilManager.prototype = Object.create(WebGLManager.prototype); -WebGLStencilManager.prototype.constructor = WebGLStencilManager; -module.exports = WebGLStencilManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param graphics {Graphics} - * @param webGLData {any[]} - */ -WebGLStencilManager.prototype.pushStencil = function (graphics, webGLData) { - var gl = this.renderer.gl; - - this.bindGraphics(graphics, webGLData, this.renderer); - - if (this.stencilStack.length === 0) { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - this.reverse = true; - this.count = 0; - } - - this.stencilStack.push(webGLData); - - var level = this.count; - - gl.colorMask(false, false, false, false); - - gl.stencilFunc(gl.ALWAYS,0,0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); - - // draw the triangle strip! - - if (webGLData.mode === 1) { - gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); - - if (this.reverse) { - gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); - } - else { - gl.stencilFunc(gl.EQUAL,level, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - } - - // draw a quad to increment.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - if (this.reverse) { - gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); - } - else { - gl.stencilFunc(gl.EQUAL,level+1, 0xFF); - } - - this.reverse = !this.reverse; - } - else { - if (!this.reverse) { - gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); - } - else { - gl.stencilFunc(gl.EQUAL,level, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - } - - gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); - - if (!this.reverse) { - gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); - } - else { - gl.stencilFunc(gl.EQUAL,level+1, 0xFF); - } - } - - gl.colorMask(true, true, true, true); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); - - this.count++; -}; - -/** - * TODO this does not belong here! - * - * @param graphics {Graphics} - * @param webGLData {Array} - */ -WebGLStencilManager.prototype.bindGraphics = function (graphics, webGLData) { - //if (this._currentGraphics === graphics)return; - this._currentGraphics = graphics; - - var gl = this.renderer.gl; - - // bind the graphics object.. - var projection = this.renderer.projection, - offset = this.renderer.offset, - shader;// = this.renderer.shaderManager.primitiveShader; - - if (webGLData.mode === 1) { - shader = this.renderer.shaderManager.complexPrimitiveShader; - - this.renderer.shaderManager.setShader(shader); - - gl.uniform1f(shader.flipY, this.renderer.flipY); - - gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); - - gl.uniform2f(shader.projectionVector, projection.x, -projection.y); - gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); - - gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); - gl.uniform3fv(shader.color, webGLData.color); - - gl.uniform1f(shader.alpha, graphics.worldAlpha * webGLData.alpha); - - gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); - - gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 2, 0); - - - // now do the rest.. - // set the index buffer! - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); - } - else { - //this.renderer.shaderManager.activatePrimitiveShader(); - shader = this.renderer.shaderManager.primitiveShader; - this.renderer.shaderManager.setShader( shader ); - - gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); - - gl.uniform1f(shader.flipY, this.renderer.flipY); - gl.uniform2f(shader.projectionVector, projection.x, -projection.y); - gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); - - gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); - - gl.uniform1f(shader.alpha, graphics.worldAlpha); - - gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); - - gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); - gl.vertexAttribPointer(shader.colorAttribute, 4, gl.FLOAT, false,4 * 6, 2 * 4); - - // set the index buffer! - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); - } -}; - -/** - * @param graphics {Graphics} - * @param webGLData {Array} - */ -WebGLStencilManager.prototype.popStencil = function (graphics, webGLData) { - var gl = this.renderer.gl; - - this.stencilStack.pop(); - - this.count--; - - if (this.stencilStack.length === 0) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - - } - else { - - var level = this.count; - - this.bindGraphics(graphics, webGLData, this.renderer); - - gl.colorMask(false, false, false, false); - - if (webGLData.mode === 1) { - this.reverse = !this.reverse; - - if (this.reverse) { - gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - } - else { - gl.stencilFunc(gl.EQUAL,level+1, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); - } - - // draw a quad to increment.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - gl.stencilFunc(gl.ALWAYS,0,0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); - - // draw the triangle strip! - gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); - - if (!this.reverse) { - gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); - } - else { - gl.stencilFunc(gl.EQUAL,level, 0xFF); - } - - } - else { - // console.log("<<>>") - if (!this.reverse) { - gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - } - else { - gl.stencilFunc(gl.EQUAL,level+1, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); - } - - gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); - - if (!this.reverse) { - gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); - } - else { - gl.stencilFunc(gl.EQUAL,level, 0xFF); - } - } - - gl.colorMask(true, true, true, true); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); - - - } -}; - -/** - * Destroys the mask stack. - * - */ -WebGLStencilManager.prototype.destroy = function () { - this.renderer = null; - this.stencilStack = null; -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js new file mode 100644 index 0000000..b992c31 --- /dev/null +++ b/src/core/textures/VideoBaseTexture.js @@ -0,0 +1,138 @@ +var BaseTexture = require('./BaseTexture'), + utils = require('../utils'); + +/** + * A texture of a [playing] Video. + * + * See the ["deus" demo](http://www.goodboydigital.com/pixijs/examples/deus/). + * + * @class + * @extends BaseTexture + * @namespace PIXI + * @param source {HTMLVideoElement} + * @param [scaleMode] {number} See {@link scaleModes} for possible values + */ +function VideoBaseTexture(source, scaleMode) { + if (!source){ + throw new Error('No video source element specified.'); + } + + // hook in here to check if video is already available. + // BaseTexture looks for a source.complete boolean, plus width & height. + + if ((source.readyState === source.HAVE_ENOUGH_DATA || source.readyState === source.HAVE_FUTURE_DATA) && source.width && source.height) { + source.complete = true; + } + + BaseTexture.call(this, source, scaleMode); + + this.autoUpdate = false; + + this._boundOnUpdate = this._onUpdate.bind(this); + this._boundOnCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) { + source.addEventListener('canplay', this._boundOnCanPlay); + source.addEventListener('canplaythrough', this._boundOnCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } +} + +VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); +VideoBaseTexture.prototype.constructor = VideoBaseTexture; +module.exports = VideoBaseTexture; + +VideoBaseTexture.prototype._onUpdate = function () { + if (this.autoUpdate) { + window.requestAnimationFrame(this._boundOnUpdate); + this.needsUpdate = true; + } +}; + +VideoBaseTexture.prototype._onPlayStart = function () { + if (!this.autoUpdate) { + window.requestAnimationFrame(this._boundOnUpdate); + this.autoUpdate = true; + } +}; + +VideoBaseTexture.prototype._onPlayStop = function () { + this.autoUpdate = false; +}; + +VideoBaseTexture.prototype._onCanPlay = function () { + if (event.type === 'canplaythrough') { + this.hasLoaded = true; + + + if (this.source) { + this.source.removeEventListener('canplay', this._boundOnCanPlay); + this.source.removeEventListener('canplaythrough', this._boundOnCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + // prevent multiple loaded dispatches.. + if (!this.__loaded){ + this.__loaded = true; + this.dispatchEvent({ type: 'loaded', content: this }); + } + } + } +}; + +VideoBaseTexture.prototype.destroy = function () { + if (this.source && this.source._pixiId) { + utils.BaseTextureCache[ this.source._pixiId ] = null; + delete utils.BaseTextureCache[ this.source._pixiId ]; + + this.source._pixiId = null; + delete this.source._pixiId; + } + + BaseTexture.prototype.destroy.call(this); +}; + +/** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} + * @param scaleMode {number} See {@link scaleModes} for possible values + * @return {VideoBaseTexture} + */ +VideoBaseTexture.fromVideo = function (video, scaleMode) { + if (!video._pixiId) { + video._pixiId = 'video_' + utils.uuid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; +}; + +/** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param videoSrc {string} The URL for the video. + * @param scaleMode {number} See {@link scaleModes} for possible values + * @return {VideoBaseTexture} + */ +VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) { + var video = document.createElement('video'); + + video.src = videoSrc; + video.autoPlay = true; + video.play(); + + return VideoBaseTexture.textureFromVideo(video, scaleMode); +}; diff --git a/src/core/const.js b/src/core/const.js index 2803e5b..aea72ec 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -130,5 +130,25 @@ backgroundColor: 0x000000, clearBeforeRender: true, preserveDrawingBuffer: false + }, + + /** + * Constants that identify shapes, mainly to prevent `instanceof` calls. + * + * @static + * @constant + * @property {object} SHAPES + * @property {object} SHAPES.POLY=0 + * @property {object} SHAPES.RECT=1 + * @property {object} SHAPES.CIRC=2 + * @property {object} SHAPES.ELIP=3 + * @property {object} SHAPES.RREC=4 + */ + SHAPES: { + POLY = 0; + RECT = 1; + CIRC = 2; + ELIP = 3; + RREC = 4; } }; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index bfd59cf..7c0e7c6 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -95,7 +95,7 @@ this.filterArea = null; /** - * cached sin rotation and cos rotation + * cached sin rotation * * @member {number} * @private @@ -103,7 +103,7 @@ this._sr = 0; /** - * cached sin rotation and cos rotation + * cached cos rotation * * @member {number} * @private @@ -400,7 +400,7 @@ DisplayObject.prototype.generateTexture = function (resolution, scaleMode, renderer) { var bounds = this.getLocalBounds(); - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); _tempMatrix.tx = -bounds.x; _tempMatrix.ty = -bounds.y; @@ -474,7 +474,7 @@ var bounds = this.getLocalBounds(); if (!this._cachedSprite) { - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0); //, renderer); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0); this._cachedSprite = new Sprite(renderTexture); this._cachedSprite.worldTransform = this.worldTransform; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 3f6a3b6..69cfdc9 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Circle object can be used to specify a hit area for displayObjects @@ -29,10 +30,11 @@ this.radius = radius || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.CIRC in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.CIRC; } Circle.prototype.constructor = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index fc9df4d..4f2d066 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Ellipse object can be used to specify a hit area for displayObjects @@ -36,10 +37,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.ELIP in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.ELIP; } Ellipse.prototype.constructor = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index 5d4a464..ae89148 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -1,4 +1,5 @@ -var Point = require('../Point'); +var Point = require('../Point'), + CONST = require('../../const'); /** * @class @@ -33,6 +34,13 @@ * @member {Point[]} */ this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + */ + this.type = CONST.SHAPES.POLY; } Polygon.prototype.constructor = Polygon; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index c3454b5..16ac62a 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * the Rectangle object is an area defined by its position, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -34,10 +36,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RECT; } Rectangle.prototype.constructor = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0f2abfc..27d286e 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * The Rounded Rectangle object is an area defined by its position and has nice rounded corners, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -41,10 +43,11 @@ this.radius = radius || 20; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RRECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RREC; } RoundedRectangle.prototype.constructor = RoundedRectangle; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js new file mode 100644 index 0000000..42647c5 --- /dev/null +++ b/src/core/primitives/Graphics.js @@ -0,0 +1,1029 @@ +var core = require('../core'), + GraphicsData = require('./GraphicsData'); + +/** + * 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() { + core.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 {object[]} + * @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 = core.CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {object} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object[]} + * @private + */ + 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 core.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(core.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; + } + } + } +}); + +/** + * 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 core.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 core.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; + + 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 core.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 core.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 core.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 core.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 core.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 core.CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = core.Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + core.CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @method _renderWebGL + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderWebGL = function (renderSession) { + // 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 on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + core.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); + + return; + } + else { + renderSession.spriteBatch.stop(); + renderSession.blendModeManager.setBlendMode(this.blendMode); + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + if (this._filters) { + renderSession.filterManager.pushFilter(this._filterBlock); + } + + // check blend mode + if (this.blendMode !== renderSession.spriteBatch.currentBlendMode) { + renderSession.spriteBatch.currentBlendMode = this.blendMode; + + var blendModeWebGL = blendModesWebGL[renderSession.spriteBatch.currentBlendMode]; + + renderSession.spriteBatch.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); + } + + // check if the webgl graphic needs to be updated + if (this.glDirty) { + this.dirty = true; + this.glDirty = false; + } + + core.WebGLGraphics.renderGraphics(this, renderSession); + + // only render if it has children! + if (this.children.length) { + renderSession.spriteBatch.start(); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderWebGL(renderSession); + } + + renderSession.spriteBatch.stop(); + } + + if (this._filters) { + renderSession.filterManager.popFilter(); + } + + if (this._mask) { + renderSession.maskManager.popMask(this.mask, renderSession); + } + + renderSession.drawCount++; + + renderSession.spriteBatch.start(); + } +}; + +/** + * Renders the object using the Canvas renderer + * + * @method _renderCanvas + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderCanvas = function (renderSession) { + // 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; + + core.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession); + + return; + } + else { + var context = renderSession.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderSession.currentBlendMode) { + renderSession.currentBlendMode = this.blendMode; + context.globalCompositeOperation = blendModesCanvas[renderSession.currentBlendMode]; + } + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + var resolution = renderSession.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + core.CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderCanvas(renderSession); + } + + if (this._mask) { + renderSession.maskManager.popMask(renderSession); + } + } +}; + +/** + * 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 core.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 === core.CONST.SHAPES.RECT || type === core.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 === core.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 === core.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 core.CanvasBuffer(bounds.width, bounds.height); + var texture = core.Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new core.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.. + core.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 === core.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 new file mode 100644 index 0000000..9377ef7 --- /dev/null +++ b/src/core/primitives/GraphicsData.js @@ -0,0 +1,23 @@ +/** + * 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; diff --git a/src/core/primitives/index.js b/src/core/primitives/index.js new file mode 100644 index 0000000..7f02649 --- /dev/null +++ b/src/core/primitives/index.js @@ -0,0 +1,14 @@ +/** + * @file Main export of the PIXI primitives library + * @author Mat Groves + * @copyright 2013-2015 GoodBoyDigital + * @license {@link https://github.com/GoodBoyDigital/pixi.js/blob/master/LICENSE|MIT License} + */ + +/** + * @namespace PIXI + */ +module.exports = { + Graphics: require('./Graphics'), + GraphicsData: require('./GraphicsData') +}; diff --git a/src/core/renderers/canvas/CanvasGraphics.js b/src/core/renderers/canvas/CanvasGraphics.js index 2b1797a..ef7e765 100644 --- a/src/core/renderers/canvas/CanvasGraphics.js +++ b/src/core/renderers/canvas/CanvasGraphics.js @@ -1,4 +1,4 @@ -var Graphics = require('../../../primitives/Graphics'); +var CONST = require('../../const'); /** * A set of functions used by the canvas renderer to draw the primitive graphics data. @@ -30,7 +30,7 @@ context.lineWidth = data.lineWidth; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -61,7 +61,7 @@ context.stroke(); } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { if (data.fillColor || data.fillColor === 0) { context.globalAlpha = data.fillAlpha * worldAlpha; @@ -75,7 +75,7 @@ context.strokeRect(shape.x, shape.y, shape.width, shape.height); } } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); @@ -92,7 +92,7 @@ context.stroke(); } } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas var w = shape.width * 2; @@ -130,7 +130,7 @@ context.stroke(); } } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var rx = shape.x; var ry = shape.y; var width = shape.width; @@ -190,7 +190,7 @@ var data = graphics.graphicsData[i]; var shape = data.shape; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -207,18 +207,18 @@ } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { context.beginPath(); context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); context.closePath(); } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas @@ -245,7 +245,7 @@ context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); context.closePath(); } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var pts = shape.points; var rx = pts[0]; diff --git a/src/core/renderers/webgl/managers/WebGLBlendModeManager.js b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js new file mode 100644 index 0000000..9f2f89a --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js @@ -0,0 +1,37 @@ +var WebGLManager = require('./WebGLManager'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLBlendModeManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; +} + +WebGLBlendModeManager.prototype = Object.create(WebGLManager.prototype); +WebGLBlendModeManager.prototype.constructor = WebGLBlendModeManager; +module.exports = WebGLBlendModeManager; + +/** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as BlendModes.ADD + */ +WebGLBlendModeManager.prototype.setBlendMode = function (blendMode) { + if (this.currentBlendMode === blendMode) { + return false; + } + + this.currentBlendMode = blendMode; + + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; +}; diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js new file mode 100644 index 0000000..176e7cd --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -0,0 +1,446 @@ +var WebGLManager = require('./WebGLManager'), + FilterTexture = require('./FilterTexture'), + PixiShader = require('../shaders/PixiShader'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLFilterManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {any[]} + */ + this.filterStack = []; + + /** + * @member {any[]]} + */ + this.texturePool = []; + + /** + * @member {number} + */ + this.offsetX = 0; + + /** + * @member {number} + */ + this.offsetY = 0; + + // listen for context and update necessary buffers + var self = this; + this.renderer.on('context', function () { + self.texturePool.length = 0; + self.initShaderBuffers(); + }); +} + +WebGLFilterManager.prototype = Object.create(WebGLManager.prototype); +WebGLFilterManager.prototype.constructor = WebGLFilterManager; +module.exports = WebGLFilterManager; + +/** + * @param renderer {WebGLRenderer} + * @param buffer {ArrayBuffer} + */ +WebGLFilterManager.prototype.begin = function (renderer, buffer) { + this.renderer = renderer; + this.defaultShader = renderer.shaderManager.defaultShader; + + this.width = renderer.projection.x * 2; + this.height = -renderer.projection.y * 2; + + this.buffer = buffer; +}; + +/** + * Applies the filter and adds it to the current filter stack. + * + * @param filterBlock {object} the filter that will be pushed to the current filter stack + */ +WebGLFilterManager.prototype.pushFilter = function (filterBlock) { + var gl = this.renderer.gl; + + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds(); + + // filter program + // OPTIMISATION - the first filter is free if its a simple color change? + this.filterStack.push(filterBlock); + + var filter = filterBlock.filterPasses[0]; + + this.offsetX += filterBlock._filterArea.x; + this.offsetY += filterBlock._filterArea.y; + + var texture = this.texturePool.pop(); + if (!texture) { + texture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + else { + texture.resize(this.width, this.height); + } + + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea; + + var padding = filter.padding; + filterArea.x -= padding; + filterArea.y -= padding; + filterArea.width += padding * 2; + filterArea.height += padding * 2; + + // cap filter to screen size.. + if (filterArea.x < 0) { + filterArea.x = 0; + } + + if (filterArea.width > this.width) { + filterArea.width = this.width; + } + + if (filterArea.y < 0) { + filterArea.y = 0; + } + + if (filterArea.height > this.height) { + filterArea.height = this.height; + } + + //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); + + // set view port + gl.viewport(0, 0, filterArea.width, filterArea.height); + + projection.x = filterArea.width/2; + projection.y = -filterArea.height/2; + + offset.x = -filterArea.x; + offset.y = -filterArea.y; + + // update projection + // now restore the regular shader.. + // this.renderer.shaderManager.setShader(this.defaultShader); + //gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); + //gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y); + + gl.colorMask(true, true, true, true); + gl.clearColor(0,0,0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + + filterBlock._glFilterTexture = texture; + +}; + +/** + * Removes the last filter from the filter stack and doesn't return it. + * + */ +WebGLFilterManager.prototype.popFilter = function () { + var gl = this.renderer.gl; + + var filterBlock = this.filterStack.pop(); + var filterArea = filterBlock._filterArea; + var texture = filterBlock._glFilterTexture; + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + if (filterBlock.filterPasses.length > 1) { + gl.viewport(0, 0, filterArea.width, filterArea.height); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = 0; + this.vertexArray[1] = filterArea.height; + + this.vertexArray[2] = filterArea.width; + this.vertexArray[3] = filterArea.height; + + this.vertexArray[4] = 0; + this.vertexArray[5] = 0; + + this.vertexArray[6] = filterArea.width; + this.vertexArray[7] = 0; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + // now set the uvs.. + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + var inputTexture = texture; + var outputTexture = this.texturePool.pop(); + if (!outputTexture) { + outputTexture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + outputTexture.resize(this.width, this.height); + + // need to clear this FBO as it may have some left over elements from a previous filter. + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + gl.clear(gl.COLOR_BUFFER_BIT); + + gl.disable(gl.BLEND); + + for (var i = 0; i < filterBlock.filterPasses.length-1; i++) { + var filterPass = filterBlock.filterPasses[i]; + + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); + + // draw texture.. + //filterPass.applyFilterPass(filterArea.width, filterArea.height); + this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height); + + // swap the textures.. + var temp = inputTexture; + inputTexture = outputTexture; + outputTexture = temp; + } + + gl.enable(gl.BLEND); + + texture = inputTexture; + this.texturePool.push(outputTexture); + } + + var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1]; + + this.offsetX -= filterArea.x; + this.offsetY -= filterArea.y; + + var sizeX = this.width; + var sizeY = this.height; + + var offsetX = 0; + var offsetY = 0; + + var buffer = this.buffer; + + // time to render the filters texture to the previous scene + if (this.filterStack.length === 0) { + gl.colorMask(true, true, true, true);//this.transparent); + } + else { + var currentFilter = this.filterStack[this.filterStack.length-1]; + filterArea = currentFilter._filterArea; + + sizeX = filterArea.width; + sizeY = filterArea.height; + + offsetX = filterArea.x; + offsetY = filterArea.y; + + buffer = currentFilter._glFilterTexture.frameBuffer; + } + + // TODO need to remove these global elements.. + projection.x = sizeX/2; + projection.y = -sizeY/2; + + offset.x = offsetX; + offset.y = offsetY; + + filterArea = filterBlock._filterArea; + + var x = filterArea.x-offsetX; + var y = filterArea.y-offsetY; + + // update the buffers.. + // make sure to flip the y! + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = x; + this.vertexArray[1] = y + filterArea.height; + + this.vertexArray[2] = x + filterArea.width; + this.vertexArray[3] = y + filterArea.height; + + this.vertexArray[4] = x; + this.vertexArray[5] = y; + + this.vertexArray[6] = x + filterArea.width; + this.vertexArray[7] = y; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + gl.viewport(0, 0, sizeX, sizeY); + + // bind the buffer + gl.bindFramebuffer(gl.FRAMEBUFFER, buffer ); + + // set the blend mode! + //gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + // apply! + this.applyFilterPass(filter, filterArea, sizeX, sizeY); + + // now restore the regular shader.. should happen automatically now.. + // this.renderer.shaderManager.setShader(this.defaultShader); + // gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2); + // gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY); + + // return the texture to the pool + this.texturePool.push(texture); + filterBlock._glFilterTexture = null; +}; + + +/** + * Applies the filter to the specified area. + * + * @param filter {AbstractFilter} the filter that needs to be applied + * @param filterArea {Texture} TODO - might need an update + * @param width {number} the horizontal range of the filter + * @param height {number} the vertical range of the filter + */ +WebGLFilterManager.prototype.applyFilterPass = function (filter, filterArea, width, height) { + // use program + var gl = this.renderer.gl; + + var shader = filter.shaders[gl.id]; + + if (!shader) { + shader = new PixiShader(gl); + + shader.fragmentSrc = filter.fragmentSrc; + shader.uniforms = filter.uniforms; + shader.init(); + + filter.shaders[gl.id] = shader; + } + + // set the shader + this.renderer.shaderManager.setShader(shader); + +// gl.useProgram(shader.program); + + gl.uniform2f(shader.projectionVector, width/2, -height/2); + gl.uniform2f(shader.offsetVector, 0,0); + + if (filter.uniforms.dimensions) { + filter.uniforms.dimensions.value[0] = this.width;//width; + filter.uniforms.dimensions.value[1] = this.height;//height; + filter.uniforms.dimensions.value[2] = this.vertexArray[0]; + filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; + } + + shader.syncUniforms(); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + + // draw the filter... + gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); + + this.renderer.drawCount++; +}; + +/** + * Initialises the shader buffers. + * + */ +WebGLFilterManager.prototype.initShaderBuffers = function () { + var gl = this.renderer.gl; + + // create some buffers + this.vertexBuffer = gl.createBuffer(); + this.uvBuffer = gl.createBuffer(); + this.colorBuffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + + // bind and upload the vertexs.. + // keep a reference to the vertexFloatData.. + this.vertexArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW); + + // bind and upload the uv buffer + this.uvArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.uvArray, gl.STATIC_DRAW); + + this.colorArray = new Float32Array([1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.colorArray, gl.STATIC_DRAW); + + // bind and upload the index + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW); + +}; + +/** + * Destroys the filter and removes it from the filter stack. + * + */ +WebGLFilterManager.prototype.destroy = function () { + var gl = this.renderer.gl; + + this.filterStack = null; + + this.offsetX = 0; + this.offsetY = 0; + + // destroy textures + for (var i = 0; i < this.texturePool.length; i++) { + this.texturePool[i].destroy(); + } + + this.texturePool = null; + + //destroy buffers.. + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.uvBuffer); + gl.deleteBuffer(this.colorBuffer); + gl.deleteBuffer(this.indexBuffer); + + this.renderer = null; +}; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js new file mode 100644 index 0000000..9d47b5c --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -0,0 +1,20 @@ +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLManager(renderer) { + /** + * The renderer this manager works for. + * + * @member {WebGLRenderer} + */ + this.renderer = renderer; +} + +WebGLManager.prototype.constructor = WebGLManager; +module.exports = WebGLManager; + +WebGLManager.prototype.destroy = function () { + this.renderer = null; +}; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js new file mode 100644 index 0000000..6d46d85 --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -0,0 +1,41 @@ +var WebGLManager = require('./WebGLManager'), + WebGLGraphics = require('./WebGLGraphics'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLMaskManager(renderer) { + WebGLManager.call(this, renderer); +} + +WebGLMaskManager.prototype = Object.create(WebGLManager.prototype); +WebGLMaskManager.prototype.constructor = WebGLMaskManager; +module.exports = WebGLMaskManager; + +/** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {any[]} + */ +WebGLMaskManager.prototype.pushMask = function (maskData) { + if (maskData.dirty) { + WebGLGraphics.updateGraphics(maskData, this.renderer.gl); + } + + if (!maskData._webGL[this.renderer.gl.id].data.length) { + return; + } + + this.renderer.stencilManager.pushStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); +}; + +/** + * Removes the last filter from the filter stack and doesn't return it. + * + * @param maskData {any[]} + */ +WebGLMaskManager.prototype.popMask = function (maskData) { + this.renderer.stencilManager.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 new file mode 100644 index 0000000..d02d95d --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -0,0 +1,172 @@ +var WebGLManager = require('./WebGLManager'), + PrimitiveShader = require('../shaders/PrimitiveShader'), + ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), + PixiShader = require('../shaders/PixiShader'), + PixiFastShader = require('../shaders/PixiFastShader'), + StripShader = require('../shaders/StripShader'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLShaderManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {number} + */ + this.maxAttibs = 10; + + /** + * @member {any[]} + */ + this.attribState = []; + + /** + * @member {any[]} + */ + this.tempAttribState = []; + + for (var i = 0; i < this.maxAttibs; i++) { + this.attribState[i] = false; + } + + /** + * @member {any[]} + */ + this.stack = []; + + /** + * @member {number} + * @private + */ + this._currentId = -1; + + /** + * @member {Shader} + * @private + */ + 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; + + // listen for context and update necessary shaders + var self = this; + this.renderer.on('context', function (gl) { + // 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 PixiShader(gl); + + // this shader is used for the fast sprite rendering + self.fastShader = new PixiFastShader(gl); + + // the next one is used for rendering triangle strips + self.stripShader = new StripShader(gl); + + self.setShader(self.defaultShader); + }); +} + +WebGLShaderManager.prototype = Object.create(WebGLManager.prototype); +WebGLShaderManager.prototype.constructor = WebGLShaderManager; +module.exports = WebGLShaderManager; + +/** + * Takes the attributes given in parameters. + * + * @param attribs {Array} attribs + */ +WebGLShaderManager.prototype.setAttribs = function (attribs) { + // reset temp state + var i; + + for (i = 0; i < this.tempAttribState.length; i++) { + this.tempAttribState[i] = false; + } + + // set the new attribs + for (var a in attribs) { + this.tempAttribState[attribs[a]] = true; + } + + var gl = this.renderer.gl; + + for (i = 0; i < this.attribState.length; i++) { + if (this.attribState[i] !== this.tempAttribState[i]) { + this.attribState[i] = this.tempAttribState[i]; + + if (this.attribState[i]) { + gl.enableVertexAttribArray(i); + } + else { + gl.disableVertexAttribArray(i); + } + } + } +}; + +/** + * Sets the current shader. + * + * @param shader {Any} + */ +WebGLShaderManager.prototype.setShader = function (shader) { + if (this._currentId === shader.uuid) { + return false; + } + + this._currentId = shader.uuid; + + this.currentShader = shader; + + this.renderer.gl.useProgram(shader.program); + this.setAttribs(shader.attributes); + + return true; +}; + +/** + * Destroys this object. + * + */ +WebGLShaderManager.prototype.destroy = function () { + 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/managers/WebGLStencilManager.js b/src/core/renderers/webgl/managers/WebGLStencilManager.js new file mode 100644 index 0000000..0bedae6 --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLStencilManager.js @@ -0,0 +1,256 @@ +var WebGLManager = require('./WebGLManager'), + utils = require('../../../utils'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLStencilManager(renderer) { + WebGLManager.call(this, renderer); + + this.stencilStack = []; + this.reverse = true; + this.count = 0; +} + +WebGLStencilManager.prototype = Object.create(WebGLManager.prototype); +WebGLStencilManager.prototype.constructor = WebGLStencilManager; +module.exports = WebGLStencilManager; + +/** + * Applies the Mask and adds it to the current filter stack. + * + * @param graphics {Graphics} + * @param webGLData {any[]} + */ +WebGLStencilManager.prototype.pushStencil = function (graphics, webGLData) { + var gl = this.renderer.gl; + + this.bindGraphics(graphics, webGLData, this.renderer); + + if (this.stencilStack.length === 0) { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + this.reverse = true; + this.count = 0; + } + + this.stencilStack.push(webGLData); + + var level = this.count; + + gl.colorMask(false, false, false, false); + + gl.stencilFunc(gl.ALWAYS,0,0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); + + // draw the triangle strip! + + if (webGLData.mode === 1) { + gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + + // draw a quad to increment.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + } + + this.reverse = !this.reverse; + } + else { + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + } + } + + gl.colorMask(true, true, true, true); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + + this.count++; +}; + +/** + * TODO this does not belong here! + * + * @param graphics {Graphics} + * @param webGLData {Array} + */ +WebGLStencilManager.prototype.bindGraphics = function (graphics, webGLData) { + //if (this._currentGraphics === graphics)return; + this._currentGraphics = graphics; + + var gl = this.renderer.gl; + + // bind the graphics object.. + var projection = this.renderer.projection, + offset = this.renderer.offset, + shader;// = this.renderer.shaderManager.primitiveShader; + + if (webGLData.mode === 1) { + shader = this.renderer.shaderManager.complexPrimitiveShader; + + this.renderer.shaderManager.setShader(shader); + + gl.uniform1f(shader.flipY, this.renderer.flipY); + + gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); + + gl.uniform2f(shader.projectionVector, projection.x, -projection.y); + gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); + + gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); + gl.uniform3fv(shader.color, webGLData.color); + + gl.uniform1f(shader.alpha, graphics.worldAlpha * webGLData.alpha); + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 2, 0); + + + // now do the rest.. + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + } + else { + //this.renderer.shaderManager.activatePrimitiveShader(); + shader = this.renderer.shaderManager.primitiveShader; + this.renderer.shaderManager.setShader( shader ); + + gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); + + gl.uniform1f(shader.flipY, this.renderer.flipY); + gl.uniform2f(shader.projectionVector, projection.x, -projection.y); + gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); + + gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); + + gl.uniform1f(shader.alpha, graphics.worldAlpha); + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); + gl.vertexAttribPointer(shader.colorAttribute, 4, gl.FLOAT, false,4 * 6, 2 * 4); + + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + } +}; + +/** + * @param graphics {Graphics} + * @param webGLData {Array} + */ +WebGLStencilManager.prototype.popStencil = function (graphics, webGLData) { + var gl = this.renderer.gl; + + this.stencilStack.pop(); + + this.count--; + + if (this.stencilStack.length === 0) { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + + } + else { + + var level = this.count; + + this.bindGraphics(graphics, webGLData, this.renderer); + + gl.colorMask(false, false, false, false); + + if (webGLData.mode === 1) { + this.reverse = !this.reverse; + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + + // draw a quad to increment.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + gl.stencilFunc(gl.ALWAYS,0,0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); + + // draw the triangle strip! + gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + } + + } + else { + // console.log("<<>>") + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + } + } + + gl.colorMask(true, true, true, true); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + + + } +}; + +/** + * Destroys the mask stack. + * + */ +WebGLStencilManager.prototype.destroy = function () { + this.renderer = null; + this.stencilStack = null; +}; diff --git a/src/core/renderers/webgl/utils/WebGLBlendModeManager.js b/src/core/renderers/webgl/utils/WebGLBlendModeManager.js deleted file mode 100644 index 9f2f89a..0000000 --- a/src/core/renderers/webgl/utils/WebGLBlendModeManager.js +++ /dev/null @@ -1,37 +0,0 @@ -var WebGLManager = require('./WebGLManager'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLBlendModeManager(renderer) { - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -WebGLBlendModeManager.prototype = Object.create(WebGLManager.prototype); -WebGLBlendModeManager.prototype.constructor = WebGLBlendModeManager; -module.exports = WebGLBlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as BlendModes.ADD - */ -WebGLBlendModeManager.prototype.setBlendMode = function (blendMode) { - if (this.currentBlendMode === blendMode) { - return false; - } - - this.currentBlendMode = blendMode; - - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); - - return true; -}; diff --git a/src/core/renderers/webgl/utils/WebGLFilterManager.js b/src/core/renderers/webgl/utils/WebGLFilterManager.js deleted file mode 100644 index 176e7cd..0000000 --- a/src/core/renderers/webgl/utils/WebGLFilterManager.js +++ /dev/null @@ -1,446 +0,0 @@ -var WebGLManager = require('./WebGLManager'), - FilterTexture = require('./FilterTexture'), - PixiShader = require('../shaders/PixiShader'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLFilterManager(renderer) { - WebGLManager.call(this, renderer); - - /** - * @member {any[]} - */ - this.filterStack = []; - - /** - * @member {any[]]} - */ - this.texturePool = []; - - /** - * @member {number} - */ - this.offsetX = 0; - - /** - * @member {number} - */ - this.offsetY = 0; - - // listen for context and update necessary buffers - var self = this; - this.renderer.on('context', function () { - self.texturePool.length = 0; - self.initShaderBuffers(); - }); -} - -WebGLFilterManager.prototype = Object.create(WebGLManager.prototype); -WebGLFilterManager.prototype.constructor = WebGLFilterManager; -module.exports = WebGLFilterManager; - -/** - * @param renderer {WebGLRenderer} - * @param buffer {ArrayBuffer} - */ -WebGLFilterManager.prototype.begin = function (renderer, buffer) { - this.renderer = renderer; - this.defaultShader = renderer.shaderManager.defaultShader; - - this.width = renderer.projection.x * 2; - this.height = -renderer.projection.y * 2; - - this.buffer = buffer; -}; - -/** - * Applies the filter and adds it to the current filter stack. - * - * @param filterBlock {object} the filter that will be pushed to the current filter stack - */ -WebGLFilterManager.prototype.pushFilter = function (filterBlock) { - var gl = this.renderer.gl; - - var projection = this.renderer.projection; - var offset = this.renderer.offset; - - filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds(); - - // filter program - // OPTIMISATION - the first filter is free if its a simple color change? - this.filterStack.push(filterBlock); - - var filter = filterBlock.filterPasses[0]; - - this.offsetX += filterBlock._filterArea.x; - this.offsetY += filterBlock._filterArea.y; - - var texture = this.texturePool.pop(); - if (!texture) { - texture = new FilterTexture(this.renderer.gl, this.width, this.height); - } - else { - texture.resize(this.width, this.height); - } - - gl.bindTexture(gl.TEXTURE_2D, texture.texture); - - var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea; - - var padding = filter.padding; - filterArea.x -= padding; - filterArea.y -= padding; - filterArea.width += padding * 2; - filterArea.height += padding * 2; - - // cap filter to screen size.. - if (filterArea.x < 0) { - filterArea.x = 0; - } - - if (filterArea.width > this.width) { - filterArea.width = this.width; - } - - if (filterArea.y < 0) { - filterArea.y = 0; - } - - if (filterArea.height > this.height) { - filterArea.height = this.height; - } - - //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); - - // set view port - gl.viewport(0, 0, filterArea.width, filterArea.height); - - projection.x = filterArea.width/2; - projection.y = -filterArea.height/2; - - offset.x = -filterArea.x; - offset.y = -filterArea.y; - - // update projection - // now restore the regular shader.. - // this.renderer.shaderManager.setShader(this.defaultShader); - //gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); - //gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y); - - gl.colorMask(true, true, true, true); - gl.clearColor(0,0,0, 0); - gl.clear(gl.COLOR_BUFFER_BIT); - - filterBlock._glFilterTexture = texture; - -}; - -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -WebGLFilterManager.prototype.popFilter = function () { - var gl = this.renderer.gl; - - var filterBlock = this.filterStack.pop(); - var filterArea = filterBlock._filterArea; - var texture = filterBlock._glFilterTexture; - var projection = this.renderer.projection; - var offset = this.renderer.offset; - - if (filterBlock.filterPasses.length > 1) { - gl.viewport(0, 0, filterArea.width, filterArea.height); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - - this.vertexArray[0] = 0; - this.vertexArray[1] = filterArea.height; - - this.vertexArray[2] = filterArea.width; - this.vertexArray[3] = filterArea.height; - - this.vertexArray[4] = 0; - this.vertexArray[5] = 0; - - this.vertexArray[6] = filterArea.width; - this.vertexArray[7] = 0; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - // now set the uvs.. - this.uvArray[2] = filterArea.width/this.width; - this.uvArray[5] = filterArea.height/this.height; - this.uvArray[6] = filterArea.width/this.width; - this.uvArray[7] = filterArea.height/this.height; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); - - var inputTexture = texture; - var outputTexture = this.texturePool.pop(); - if (!outputTexture) { - outputTexture = new FilterTexture(this.renderer.gl, this.width, this.height); - } - outputTexture.resize(this.width, this.height); - - // need to clear this FBO as it may have some left over elements from a previous filter. - gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); - gl.clear(gl.COLOR_BUFFER_BIT); - - gl.disable(gl.BLEND); - - for (var i = 0; i < filterBlock.filterPasses.length-1; i++) { - var filterPass = filterBlock.filterPasses[i]; - - gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); - - // set texture - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); - - // draw texture.. - //filterPass.applyFilterPass(filterArea.width, filterArea.height); - this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height); - - // swap the textures.. - var temp = inputTexture; - inputTexture = outputTexture; - outputTexture = temp; - } - - gl.enable(gl.BLEND); - - texture = inputTexture; - this.texturePool.push(outputTexture); - } - - var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1]; - - this.offsetX -= filterArea.x; - this.offsetY -= filterArea.y; - - var sizeX = this.width; - var sizeY = this.height; - - var offsetX = 0; - var offsetY = 0; - - var buffer = this.buffer; - - // time to render the filters texture to the previous scene - if (this.filterStack.length === 0) { - gl.colorMask(true, true, true, true);//this.transparent); - } - else { - var currentFilter = this.filterStack[this.filterStack.length-1]; - filterArea = currentFilter._filterArea; - - sizeX = filterArea.width; - sizeY = filterArea.height; - - offsetX = filterArea.x; - offsetY = filterArea.y; - - buffer = currentFilter._glFilterTexture.frameBuffer; - } - - // TODO need to remove these global elements.. - projection.x = sizeX/2; - projection.y = -sizeY/2; - - offset.x = offsetX; - offset.y = offsetY; - - filterArea = filterBlock._filterArea; - - var x = filterArea.x-offsetX; - var y = filterArea.y-offsetY; - - // update the buffers.. - // make sure to flip the y! - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - - this.vertexArray[0] = x; - this.vertexArray[1] = y + filterArea.height; - - this.vertexArray[2] = x + filterArea.width; - this.vertexArray[3] = y + filterArea.height; - - this.vertexArray[4] = x; - this.vertexArray[5] = y; - - this.vertexArray[6] = x + filterArea.width; - this.vertexArray[7] = y; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - - this.uvArray[2] = filterArea.width/this.width; - this.uvArray[5] = filterArea.height/this.height; - this.uvArray[6] = filterArea.width/this.width; - this.uvArray[7] = filterArea.height/this.height; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); - - gl.viewport(0, 0, sizeX, sizeY); - - // bind the buffer - gl.bindFramebuffer(gl.FRAMEBUFFER, buffer ); - - // set the blend mode! - //gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) - - // set texture - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, texture.texture); - - // apply! - this.applyFilterPass(filter, filterArea, sizeX, sizeY); - - // now restore the regular shader.. should happen automatically now.. - // this.renderer.shaderManager.setShader(this.defaultShader); - // gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2); - // gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY); - - // return the texture to the pool - this.texturePool.push(texture); - filterBlock._glFilterTexture = null; -}; - - -/** - * Applies the filter to the specified area. - * - * @param filter {AbstractFilter} the filter that needs to be applied - * @param filterArea {Texture} TODO - might need an update - * @param width {number} the horizontal range of the filter - * @param height {number} the vertical range of the filter - */ -WebGLFilterManager.prototype.applyFilterPass = function (filter, filterArea, width, height) { - // use program - var gl = this.renderer.gl; - - var shader = filter.shaders[gl.id]; - - if (!shader) { - shader = new PixiShader(gl); - - shader.fragmentSrc = filter.fragmentSrc; - shader.uniforms = filter.uniforms; - shader.init(); - - filter.shaders[gl.id] = shader; - } - - // set the shader - this.renderer.shaderManager.setShader(shader); - -// gl.useProgram(shader.program); - - gl.uniform2f(shader.projectionVector, width/2, -height/2); - gl.uniform2f(shader.offsetVector, 0,0); - - if (filter.uniforms.dimensions) { - filter.uniforms.dimensions.value[0] = this.width;//width; - filter.uniforms.dimensions.value[1] = this.height;//height; - filter.uniforms.dimensions.value[2] = this.vertexArray[0]; - filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; - } - - shader.syncUniforms(); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); - gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - - // draw the filter... - gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); - - this.renderer.drawCount++; -}; - -/** - * Initialises the shader buffers. - * - */ -WebGLFilterManager.prototype.initShaderBuffers = function () { - var gl = this.renderer.gl; - - // create some buffers - this.vertexBuffer = gl.createBuffer(); - this.uvBuffer = gl.createBuffer(); - this.colorBuffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - - // bind and upload the vertexs.. - // keep a reference to the vertexFloatData.. - this.vertexArray = new Float32Array([0.0, 0.0, - 1.0, 0.0, - 0.0, 1.0, - 1.0, 1.0]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW); - - // bind and upload the uv buffer - this.uvArray = new Float32Array([0.0, 0.0, - 1.0, 0.0, - 0.0, 1.0, - 1.0, 1.0]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.uvArray, gl.STATIC_DRAW); - - this.colorArray = new Float32Array([1.0, 0xFFFFFF, - 1.0, 0xFFFFFF, - 1.0, 0xFFFFFF, - 1.0, 0xFFFFFF]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.colorArray, gl.STATIC_DRAW); - - // bind and upload the index - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW); - -}; - -/** - * Destroys the filter and removes it from the filter stack. - * - */ -WebGLFilterManager.prototype.destroy = function () { - var gl = this.renderer.gl; - - this.filterStack = null; - - this.offsetX = 0; - this.offsetY = 0; - - // destroy textures - for (var i = 0; i < this.texturePool.length; i++) { - this.texturePool[i].destroy(); - } - - this.texturePool = null; - - //destroy buffers.. - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.uvBuffer); - gl.deleteBuffer(this.colorBuffer); - gl.deleteBuffer(this.indexBuffer); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/WebGLGraphics.js b/src/core/renderers/webgl/utils/WebGLGraphics.js index 09f540f..1d1fe40 100644 --- a/src/core/renderers/webgl/utils/WebGLGraphics.js +++ b/src/core/renderers/webgl/utils/WebGLGraphics.js @@ -1,6 +1,5 @@ var utils = require('../../../utils'), math = require('../../../math'), - Graphics = require('../../../../primitives/Graphics'), WebGLGraphicsData = require('./WebGLGraphicsData'); /** @@ -122,7 +121,7 @@ for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) { var data = graphics.graphicsData[i]; - if (data.type === Graphics.POLY) { + 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) { @@ -164,13 +163,13 @@ else { webGLData = WebGLGraphics.switchMode(webGL, 0); - if (data.type === Graphics.RECT) { + if (data.type === CONST.SHAPES.RECT) { WebGLGraphics.buildRectangle(data, webGLData); } - else if (data.type === Graphics.CIRC || data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) { WebGLGraphics.buildCircle(data, webGLData); } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { WebGLGraphics.buildRoundedRectangle(data, webGLData); } } @@ -415,7 +414,7 @@ var height; // TODO - bit hacky?? - if (graphicsData.type === Graphics.CIRC) { + if (graphicsData.type === CONST.SHAPES.CIRC) { width = circleData.radius; height = circleData.radius; } diff --git a/src/core/renderers/webgl/utils/WebGLManager.js b/src/core/renderers/webgl/utils/WebGLManager.js deleted file mode 100644 index 9d47b5c..0000000 --- a/src/core/renderers/webgl/utils/WebGLManager.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLManager(renderer) { - /** - * The renderer this manager works for. - * - * @member {WebGLRenderer} - */ - this.renderer = renderer; -} - -WebGLManager.prototype.constructor = WebGLManager; -module.exports = WebGLManager; - -WebGLManager.prototype.destroy = function () { - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/WebGLMaskManager.js b/src/core/renderers/webgl/utils/WebGLMaskManager.js deleted file mode 100644 index 6d46d85..0000000 --- a/src/core/renderers/webgl/utils/WebGLMaskManager.js +++ /dev/null @@ -1,41 +0,0 @@ -var WebGLManager = require('./WebGLManager'), - WebGLGraphics = require('./WebGLGraphics'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLMaskManager(renderer) { - WebGLManager.call(this, renderer); -} - -WebGLMaskManager.prototype = Object.create(WebGLManager.prototype); -WebGLMaskManager.prototype.constructor = WebGLMaskManager; -module.exports = WebGLMaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {any[]} - */ -WebGLMaskManager.prototype.pushMask = function (maskData) { - if (maskData.dirty) { - WebGLGraphics.updateGraphics(maskData, this.renderer.gl); - } - - if (!maskData._webGL[this.renderer.gl.id].data.length) { - return; - } - - this.renderer.stencilManager.pushStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); -}; - -/** - * Removes the last filter from the filter stack and doesn't return it. - * - * @param maskData {any[]} - */ -WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.stencilManager.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); -}; diff --git a/src/core/renderers/webgl/utils/WebGLShaderManager.js b/src/core/renderers/webgl/utils/WebGLShaderManager.js deleted file mode 100644 index d02d95d..0000000 --- a/src/core/renderers/webgl/utils/WebGLShaderManager.js +++ /dev/null @@ -1,172 +0,0 @@ -var WebGLManager = require('./WebGLManager'), - PrimitiveShader = require('../shaders/PrimitiveShader'), - ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), - PixiShader = require('../shaders/PixiShader'), - PixiFastShader = require('../shaders/PixiFastShader'), - StripShader = require('../shaders/StripShader'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLShaderManager(renderer) { - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.maxAttibs = 10; - - /** - * @member {any[]} - */ - this.attribState = []; - - /** - * @member {any[]} - */ - this.tempAttribState = []; - - for (var i = 0; i < this.maxAttibs; i++) { - this.attribState[i] = false; - } - - /** - * @member {any[]} - */ - this.stack = []; - - /** - * @member {number} - * @private - */ - this._currentId = -1; - - /** - * @member {Shader} - * @private - */ - 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; - - // listen for context and update necessary shaders - var self = this; - this.renderer.on('context', function (gl) { - // 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 PixiShader(gl); - - // this shader is used for the fast sprite rendering - self.fastShader = new PixiFastShader(gl); - - // the next one is used for rendering triangle strips - self.stripShader = new StripShader(gl); - - self.setShader(self.defaultShader); - }); -} - -WebGLShaderManager.prototype = Object.create(WebGLManager.prototype); -WebGLShaderManager.prototype.constructor = WebGLShaderManager; -module.exports = WebGLShaderManager; - -/** - * Takes the attributes given in parameters. - * - * @param attribs {Array} attribs - */ -WebGLShaderManager.prototype.setAttribs = function (attribs) { - // reset temp state - var i; - - for (i = 0; i < this.tempAttribState.length; i++) { - this.tempAttribState[i] = false; - } - - // set the new attribs - for (var a in attribs) { - this.tempAttribState[attribs[a]] = true; - } - - var gl = this.renderer.gl; - - for (i = 0; i < this.attribState.length; i++) { - if (this.attribState[i] !== this.tempAttribState[i]) { - this.attribState[i] = this.tempAttribState[i]; - - if (this.attribState[i]) { - gl.enableVertexAttribArray(i); - } - else { - gl.disableVertexAttribArray(i); - } - } - } -}; - -/** - * Sets the current shader. - * - * @param shader {Any} - */ -WebGLShaderManager.prototype.setShader = function (shader) { - if (this._currentId === shader.uuid) { - return false; - } - - this._currentId = shader.uuid; - - this.currentShader = shader; - - this.renderer.gl.useProgram(shader.program); - this.setAttribs(shader.attributes); - - return true; -}; - -/** - * Destroys this object. - * - */ -WebGLShaderManager.prototype.destroy = function () { - 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/utils/WebGLStencilManager.js b/src/core/renderers/webgl/utils/WebGLStencilManager.js deleted file mode 100644 index 0bedae6..0000000 --- a/src/core/renderers/webgl/utils/WebGLStencilManager.js +++ /dev/null @@ -1,256 +0,0 @@ -var WebGLManager = require('./WebGLManager'), - utils = require('../../../utils'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLStencilManager(renderer) { - WebGLManager.call(this, renderer); - - this.stencilStack = []; - this.reverse = true; - this.count = 0; -} - -WebGLStencilManager.prototype = Object.create(WebGLManager.prototype); -WebGLStencilManager.prototype.constructor = WebGLStencilManager; -module.exports = WebGLStencilManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param graphics {Graphics} - * @param webGLData {any[]} - */ -WebGLStencilManager.prototype.pushStencil = function (graphics, webGLData) { - var gl = this.renderer.gl; - - this.bindGraphics(graphics, webGLData, this.renderer); - - if (this.stencilStack.length === 0) { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - this.reverse = true; - this.count = 0; - } - - this.stencilStack.push(webGLData); - - var level = this.count; - - gl.colorMask(false, false, false, false); - - gl.stencilFunc(gl.ALWAYS,0,0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); - - // draw the triangle strip! - - if (webGLData.mode === 1) { - gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); - - if (this.reverse) { - gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); - } - else { - gl.stencilFunc(gl.EQUAL,level, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - } - - // draw a quad to increment.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - if (this.reverse) { - gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); - } - else { - gl.stencilFunc(gl.EQUAL,level+1, 0xFF); - } - - this.reverse = !this.reverse; - } - else { - if (!this.reverse) { - gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); - } - else { - gl.stencilFunc(gl.EQUAL,level, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - } - - gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); - - if (!this.reverse) { - gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); - } - else { - gl.stencilFunc(gl.EQUAL,level+1, 0xFF); - } - } - - gl.colorMask(true, true, true, true); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); - - this.count++; -}; - -/** - * TODO this does not belong here! - * - * @param graphics {Graphics} - * @param webGLData {Array} - */ -WebGLStencilManager.prototype.bindGraphics = function (graphics, webGLData) { - //if (this._currentGraphics === graphics)return; - this._currentGraphics = graphics; - - var gl = this.renderer.gl; - - // bind the graphics object.. - var projection = this.renderer.projection, - offset = this.renderer.offset, - shader;// = this.renderer.shaderManager.primitiveShader; - - if (webGLData.mode === 1) { - shader = this.renderer.shaderManager.complexPrimitiveShader; - - this.renderer.shaderManager.setShader(shader); - - gl.uniform1f(shader.flipY, this.renderer.flipY); - - gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); - - gl.uniform2f(shader.projectionVector, projection.x, -projection.y); - gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); - - gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); - gl.uniform3fv(shader.color, webGLData.color); - - gl.uniform1f(shader.alpha, graphics.worldAlpha * webGLData.alpha); - - gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); - - gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 2, 0); - - - // now do the rest.. - // set the index buffer! - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); - } - else { - //this.renderer.shaderManager.activatePrimitiveShader(); - shader = this.renderer.shaderManager.primitiveShader; - this.renderer.shaderManager.setShader( shader ); - - gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); - - gl.uniform1f(shader.flipY, this.renderer.flipY); - gl.uniform2f(shader.projectionVector, projection.x, -projection.y); - gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); - - gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); - - gl.uniform1f(shader.alpha, graphics.worldAlpha); - - gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); - - gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); - gl.vertexAttribPointer(shader.colorAttribute, 4, gl.FLOAT, false,4 * 6, 2 * 4); - - // set the index buffer! - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); - } -}; - -/** - * @param graphics {Graphics} - * @param webGLData {Array} - */ -WebGLStencilManager.prototype.popStencil = function (graphics, webGLData) { - var gl = this.renderer.gl; - - this.stencilStack.pop(); - - this.count--; - - if (this.stencilStack.length === 0) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - - } - else { - - var level = this.count; - - this.bindGraphics(graphics, webGLData, this.renderer); - - gl.colorMask(false, false, false, false); - - if (webGLData.mode === 1) { - this.reverse = !this.reverse; - - if (this.reverse) { - gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - } - else { - gl.stencilFunc(gl.EQUAL,level+1, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); - } - - // draw a quad to increment.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - gl.stencilFunc(gl.ALWAYS,0,0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); - - // draw the triangle strip! - gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); - - if (!this.reverse) { - gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); - } - else { - gl.stencilFunc(gl.EQUAL,level, 0xFF); - } - - } - else { - // console.log("<<>>") - if (!this.reverse) { - gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - } - else { - gl.stencilFunc(gl.EQUAL,level+1, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); - } - - gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); - - if (!this.reverse) { - gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); - } - else { - gl.stencilFunc(gl.EQUAL,level, 0xFF); - } - } - - gl.colorMask(true, true, true, true); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); - - - } -}; - -/** - * Destroys the mask stack. - * - */ -WebGLStencilManager.prototype.destroy = function () { - this.renderer = null; - this.stencilStack = null; -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js new file mode 100644 index 0000000..b992c31 --- /dev/null +++ b/src/core/textures/VideoBaseTexture.js @@ -0,0 +1,138 @@ +var BaseTexture = require('./BaseTexture'), + utils = require('../utils'); + +/** + * A texture of a [playing] Video. + * + * See the ["deus" demo](http://www.goodboydigital.com/pixijs/examples/deus/). + * + * @class + * @extends BaseTexture + * @namespace PIXI + * @param source {HTMLVideoElement} + * @param [scaleMode] {number} See {@link scaleModes} for possible values + */ +function VideoBaseTexture(source, scaleMode) { + if (!source){ + throw new Error('No video source element specified.'); + } + + // hook in here to check if video is already available. + // BaseTexture looks for a source.complete boolean, plus width & height. + + if ((source.readyState === source.HAVE_ENOUGH_DATA || source.readyState === source.HAVE_FUTURE_DATA) && source.width && source.height) { + source.complete = true; + } + + BaseTexture.call(this, source, scaleMode); + + this.autoUpdate = false; + + this._boundOnUpdate = this._onUpdate.bind(this); + this._boundOnCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) { + source.addEventListener('canplay', this._boundOnCanPlay); + source.addEventListener('canplaythrough', this._boundOnCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } +} + +VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); +VideoBaseTexture.prototype.constructor = VideoBaseTexture; +module.exports = VideoBaseTexture; + +VideoBaseTexture.prototype._onUpdate = function () { + if (this.autoUpdate) { + window.requestAnimationFrame(this._boundOnUpdate); + this.needsUpdate = true; + } +}; + +VideoBaseTexture.prototype._onPlayStart = function () { + if (!this.autoUpdate) { + window.requestAnimationFrame(this._boundOnUpdate); + this.autoUpdate = true; + } +}; + +VideoBaseTexture.prototype._onPlayStop = function () { + this.autoUpdate = false; +}; + +VideoBaseTexture.prototype._onCanPlay = function () { + if (event.type === 'canplaythrough') { + this.hasLoaded = true; + + + if (this.source) { + this.source.removeEventListener('canplay', this._boundOnCanPlay); + this.source.removeEventListener('canplaythrough', this._boundOnCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + // prevent multiple loaded dispatches.. + if (!this.__loaded){ + this.__loaded = true; + this.dispatchEvent({ type: 'loaded', content: this }); + } + } + } +}; + +VideoBaseTexture.prototype.destroy = function () { + if (this.source && this.source._pixiId) { + utils.BaseTextureCache[ this.source._pixiId ] = null; + delete utils.BaseTextureCache[ this.source._pixiId ]; + + this.source._pixiId = null; + delete this.source._pixiId; + } + + BaseTexture.prototype.destroy.call(this); +}; + +/** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} + * @param scaleMode {number} See {@link scaleModes} for possible values + * @return {VideoBaseTexture} + */ +VideoBaseTexture.fromVideo = function (video, scaleMode) { + if (!video._pixiId) { + video._pixiId = 'video_' + utils.uuid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; +}; + +/** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param videoSrc {string} The URL for the video. + * @param scaleMode {number} See {@link scaleModes} for possible values + * @return {VideoBaseTexture} + */ +VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) { + var video = document.createElement('video'); + + video.src = videoSrc; + video.autoPlay = true; + video.play(); + + return VideoBaseTexture.textureFromVideo(video, scaleMode); +}; diff --git a/src/primitives/Graphics.js b/src/primitives/Graphics.js deleted file mode 100644 index 7283ec5..0000000 --- a/src/primitives/Graphics.js +++ /dev/null @@ -1,1067 +0,0 @@ -var core = require('../core'), - GraphicsData = require('./GraphicsData'); - -/** - * 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() { - core.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 {object[]} - * @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 = core.CONST.blendModes.NORMAL; - - /** - * Current path - * - * @member {object} - * @private - */ - this.currentPath = null; - - /** - * Array containing some WebGL-related properties used by the WebGL renderer. - * - * @member {object[]} - * @private - */ - 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 core.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(core.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; - } - } - } -}); - -/** - * 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 core.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 core.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; - - 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 core.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 core.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 core.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 core.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 core.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 core.CanvasBuffer(bounds.width * resolution, bounds.height * resolution); - - var texture = core.Texture.fromCanvas(canvasBuffer.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - canvasBuffer.context.scale(resolution, resolution); - - canvasBuffer.context.translate(-bounds.x,-bounds.y); - - core.CanvasGraphics.renderGraphics(this, canvasBuffer.context); - - return texture; -}; - -/** - * Renders the object using the WebGL renderer - * - * @method _renderWebGL - * @param renderSession {RenderSession} - * @private - */ -Graphics.prototype._renderWebGL = function (renderSession) { - // 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 on the gpu too! - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.worldAlpha = this.worldAlpha; - - core.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); - - return; - } - else { - renderSession.spriteBatch.stop(); - renderSession.blendModeManager.setBlendMode(this.blendMode); - - if (this._mask) { - renderSession.maskManager.pushMask(this._mask, renderSession); - } - - if (this._filters) { - renderSession.filterManager.pushFilter(this._filterBlock); - } - - // check blend mode - if (this.blendMode !== renderSession.spriteBatch.currentBlendMode) { - renderSession.spriteBatch.currentBlendMode = this.blendMode; - - var blendModeWebGL = blendModesWebGL[renderSession.spriteBatch.currentBlendMode]; - - renderSession.spriteBatch.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); - } - - // check if the webgl graphic needs to be updated - if (this.glDirty) { - this.dirty = true; - this.glDirty = false; - } - - core.WebGLGraphics.renderGraphics(this, renderSession); - - // only render if it has children! - if (this.children.length) { - renderSession.spriteBatch.start(); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) { - this.children[i]._renderWebGL(renderSession); - } - - renderSession.spriteBatch.stop(); - } - - if (this._filters) { - renderSession.filterManager.popFilter(); - } - - if (this._mask) { - renderSession.maskManager.popMask(this.mask, renderSession); - } - - renderSession.drawCount++; - - renderSession.spriteBatch.start(); - } -}; - -/** - * Renders the object using the Canvas renderer - * - * @method _renderCanvas - * @param renderSession {RenderSession} - * @private - */ -Graphics.prototype._renderCanvas = function (renderSession) { - // 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; - - core.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession); - - return; - } - else { - var context = renderSession.context; - var transform = this.worldTransform; - - if (this.blendMode !== renderSession.currentBlendMode) { - renderSession.currentBlendMode = this.blendMode; - context.globalCompositeOperation = blendModesCanvas[renderSession.currentBlendMode]; - } - - if (this._mask) { - renderSession.maskManager.pushMask(this._mask, renderSession); - } - - var resolution = renderSession.resolution; - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - core.CanvasGraphics.renderGraphics(this, context); - - for (var i = 0, j = this.children.length; i < j; ++i) { - this.children[i]._renderCanvas(renderSession); - } - - if (this._mask) { - renderSession.maskManager.popMask(renderSession); - } - } -}; - -/** - * 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 core.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 === Graphics.RECT || type === Graphics.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 === Graphics.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 === Graphics.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 core.CanvasBuffer(bounds.width, bounds.height); - var texture = core.Texture.fromCanvas(canvasBuffer.canvas); - - this._cachedSprite = new core.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.. - core.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 === Graphics.POLY) { - data.shape.closed = this.filling; - this.currentPath = data; - } - - this.dirty = true; - - return data; -}; - -/** - * @static - * @constant - */ -Graphics.POLY = 0; - -/** - * @static - * @constant - */ -Graphics.RECT = 1; - -/** - * @static - * @constant - */ -Graphics.CIRC = 2; - -/** - * @static - * @constant - */ -Graphics.ELIP = 3; - -/** - * @static - * @constant - */ -Graphics.RREC = 4; - -// REFACTOR: Move these to their classes, move types to central location. -core.math.Polygon.prototype.type = Graphics.POLY; -core.math.Rectangle.prototype.type = Graphics.RECT; -core.math.Circle.prototype.type = Graphics.CIRC; -core.math.Ellipse.prototype.type = Graphics.ELIP; -core.math.RoundedRectangle.prototype.type = Graphics.RREC; - diff --git a/src/core/const.js b/src/core/const.js index 2803e5b..aea72ec 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -130,5 +130,25 @@ backgroundColor: 0x000000, clearBeforeRender: true, preserveDrawingBuffer: false + }, + + /** + * Constants that identify shapes, mainly to prevent `instanceof` calls. + * + * @static + * @constant + * @property {object} SHAPES + * @property {object} SHAPES.POLY=0 + * @property {object} SHAPES.RECT=1 + * @property {object} SHAPES.CIRC=2 + * @property {object} SHAPES.ELIP=3 + * @property {object} SHAPES.RREC=4 + */ + SHAPES: { + POLY = 0; + RECT = 1; + CIRC = 2; + ELIP = 3; + RREC = 4; } }; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index bfd59cf..7c0e7c6 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -95,7 +95,7 @@ this.filterArea = null; /** - * cached sin rotation and cos rotation + * cached sin rotation * * @member {number} * @private @@ -103,7 +103,7 @@ this._sr = 0; /** - * cached sin rotation and cos rotation + * cached cos rotation * * @member {number} * @private @@ -400,7 +400,7 @@ DisplayObject.prototype.generateTexture = function (resolution, scaleMode, renderer) { var bounds = this.getLocalBounds(); - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); _tempMatrix.tx = -bounds.x; _tempMatrix.ty = -bounds.y; @@ -474,7 +474,7 @@ var bounds = this.getLocalBounds(); if (!this._cachedSprite) { - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0); //, renderer); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0); this._cachedSprite = new Sprite(renderTexture); this._cachedSprite.worldTransform = this.worldTransform; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 3f6a3b6..69cfdc9 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Circle object can be used to specify a hit area for displayObjects @@ -29,10 +30,11 @@ this.radius = radius || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.CIRC in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.CIRC; } Circle.prototype.constructor = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index fc9df4d..4f2d066 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Ellipse object can be used to specify a hit area for displayObjects @@ -36,10 +37,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.ELIP in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.ELIP; } Ellipse.prototype.constructor = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index 5d4a464..ae89148 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -1,4 +1,5 @@ -var Point = require('../Point'); +var Point = require('../Point'), + CONST = require('../../const'); /** * @class @@ -33,6 +34,13 @@ * @member {Point[]} */ this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + */ + this.type = CONST.SHAPES.POLY; } Polygon.prototype.constructor = Polygon; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index c3454b5..16ac62a 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * the Rectangle object is an area defined by its position, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -34,10 +36,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RECT; } Rectangle.prototype.constructor = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0f2abfc..27d286e 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * The Rounded Rectangle object is an area defined by its position and has nice rounded corners, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -41,10 +43,11 @@ this.radius = radius || 20; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RRECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RREC; } RoundedRectangle.prototype.constructor = RoundedRectangle; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js new file mode 100644 index 0000000..42647c5 --- /dev/null +++ b/src/core/primitives/Graphics.js @@ -0,0 +1,1029 @@ +var core = require('../core'), + GraphicsData = require('./GraphicsData'); + +/** + * 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() { + core.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 {object[]} + * @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 = core.CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {object} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object[]} + * @private + */ + 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 core.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(core.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; + } + } + } +}); + +/** + * 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 core.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 core.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; + + 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 core.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 core.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 core.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 core.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 core.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 core.CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = core.Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + core.CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @method _renderWebGL + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderWebGL = function (renderSession) { + // 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 on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + core.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); + + return; + } + else { + renderSession.spriteBatch.stop(); + renderSession.blendModeManager.setBlendMode(this.blendMode); + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + if (this._filters) { + renderSession.filterManager.pushFilter(this._filterBlock); + } + + // check blend mode + if (this.blendMode !== renderSession.spriteBatch.currentBlendMode) { + renderSession.spriteBatch.currentBlendMode = this.blendMode; + + var blendModeWebGL = blendModesWebGL[renderSession.spriteBatch.currentBlendMode]; + + renderSession.spriteBatch.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); + } + + // check if the webgl graphic needs to be updated + if (this.glDirty) { + this.dirty = true; + this.glDirty = false; + } + + core.WebGLGraphics.renderGraphics(this, renderSession); + + // only render if it has children! + if (this.children.length) { + renderSession.spriteBatch.start(); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderWebGL(renderSession); + } + + renderSession.spriteBatch.stop(); + } + + if (this._filters) { + renderSession.filterManager.popFilter(); + } + + if (this._mask) { + renderSession.maskManager.popMask(this.mask, renderSession); + } + + renderSession.drawCount++; + + renderSession.spriteBatch.start(); + } +}; + +/** + * Renders the object using the Canvas renderer + * + * @method _renderCanvas + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderCanvas = function (renderSession) { + // 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; + + core.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession); + + return; + } + else { + var context = renderSession.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderSession.currentBlendMode) { + renderSession.currentBlendMode = this.blendMode; + context.globalCompositeOperation = blendModesCanvas[renderSession.currentBlendMode]; + } + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + var resolution = renderSession.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + core.CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderCanvas(renderSession); + } + + if (this._mask) { + renderSession.maskManager.popMask(renderSession); + } + } +}; + +/** + * 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 core.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 === core.CONST.SHAPES.RECT || type === core.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 === core.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 === core.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 core.CanvasBuffer(bounds.width, bounds.height); + var texture = core.Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new core.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.. + core.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 === core.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 new file mode 100644 index 0000000..9377ef7 --- /dev/null +++ b/src/core/primitives/GraphicsData.js @@ -0,0 +1,23 @@ +/** + * 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; diff --git a/src/core/primitives/index.js b/src/core/primitives/index.js new file mode 100644 index 0000000..7f02649 --- /dev/null +++ b/src/core/primitives/index.js @@ -0,0 +1,14 @@ +/** + * @file Main export of the PIXI primitives library + * @author Mat Groves + * @copyright 2013-2015 GoodBoyDigital + * @license {@link https://github.com/GoodBoyDigital/pixi.js/blob/master/LICENSE|MIT License} + */ + +/** + * @namespace PIXI + */ +module.exports = { + Graphics: require('./Graphics'), + GraphicsData: require('./GraphicsData') +}; diff --git a/src/core/renderers/canvas/CanvasGraphics.js b/src/core/renderers/canvas/CanvasGraphics.js index 2b1797a..ef7e765 100644 --- a/src/core/renderers/canvas/CanvasGraphics.js +++ b/src/core/renderers/canvas/CanvasGraphics.js @@ -1,4 +1,4 @@ -var Graphics = require('../../../primitives/Graphics'); +var CONST = require('../../const'); /** * A set of functions used by the canvas renderer to draw the primitive graphics data. @@ -30,7 +30,7 @@ context.lineWidth = data.lineWidth; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -61,7 +61,7 @@ context.stroke(); } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { if (data.fillColor || data.fillColor === 0) { context.globalAlpha = data.fillAlpha * worldAlpha; @@ -75,7 +75,7 @@ context.strokeRect(shape.x, shape.y, shape.width, shape.height); } } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); @@ -92,7 +92,7 @@ context.stroke(); } } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas var w = shape.width * 2; @@ -130,7 +130,7 @@ context.stroke(); } } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var rx = shape.x; var ry = shape.y; var width = shape.width; @@ -190,7 +190,7 @@ var data = graphics.graphicsData[i]; var shape = data.shape; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -207,18 +207,18 @@ } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { context.beginPath(); context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); context.closePath(); } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas @@ -245,7 +245,7 @@ context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); context.closePath(); } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var pts = shape.points; var rx = pts[0]; diff --git a/src/core/renderers/webgl/managers/WebGLBlendModeManager.js b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js new file mode 100644 index 0000000..9f2f89a --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js @@ -0,0 +1,37 @@ +var WebGLManager = require('./WebGLManager'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLBlendModeManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; +} + +WebGLBlendModeManager.prototype = Object.create(WebGLManager.prototype); +WebGLBlendModeManager.prototype.constructor = WebGLBlendModeManager; +module.exports = WebGLBlendModeManager; + +/** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as BlendModes.ADD + */ +WebGLBlendModeManager.prototype.setBlendMode = function (blendMode) { + if (this.currentBlendMode === blendMode) { + return false; + } + + this.currentBlendMode = blendMode; + + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; +}; diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js new file mode 100644 index 0000000..176e7cd --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -0,0 +1,446 @@ +var WebGLManager = require('./WebGLManager'), + FilterTexture = require('./FilterTexture'), + PixiShader = require('../shaders/PixiShader'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLFilterManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {any[]} + */ + this.filterStack = []; + + /** + * @member {any[]]} + */ + this.texturePool = []; + + /** + * @member {number} + */ + this.offsetX = 0; + + /** + * @member {number} + */ + this.offsetY = 0; + + // listen for context and update necessary buffers + var self = this; + this.renderer.on('context', function () { + self.texturePool.length = 0; + self.initShaderBuffers(); + }); +} + +WebGLFilterManager.prototype = Object.create(WebGLManager.prototype); +WebGLFilterManager.prototype.constructor = WebGLFilterManager; +module.exports = WebGLFilterManager; + +/** + * @param renderer {WebGLRenderer} + * @param buffer {ArrayBuffer} + */ +WebGLFilterManager.prototype.begin = function (renderer, buffer) { + this.renderer = renderer; + this.defaultShader = renderer.shaderManager.defaultShader; + + this.width = renderer.projection.x * 2; + this.height = -renderer.projection.y * 2; + + this.buffer = buffer; +}; + +/** + * Applies the filter and adds it to the current filter stack. + * + * @param filterBlock {object} the filter that will be pushed to the current filter stack + */ +WebGLFilterManager.prototype.pushFilter = function (filterBlock) { + var gl = this.renderer.gl; + + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds(); + + // filter program + // OPTIMISATION - the first filter is free if its a simple color change? + this.filterStack.push(filterBlock); + + var filter = filterBlock.filterPasses[0]; + + this.offsetX += filterBlock._filterArea.x; + this.offsetY += filterBlock._filterArea.y; + + var texture = this.texturePool.pop(); + if (!texture) { + texture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + else { + texture.resize(this.width, this.height); + } + + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea; + + var padding = filter.padding; + filterArea.x -= padding; + filterArea.y -= padding; + filterArea.width += padding * 2; + filterArea.height += padding * 2; + + // cap filter to screen size.. + if (filterArea.x < 0) { + filterArea.x = 0; + } + + if (filterArea.width > this.width) { + filterArea.width = this.width; + } + + if (filterArea.y < 0) { + filterArea.y = 0; + } + + if (filterArea.height > this.height) { + filterArea.height = this.height; + } + + //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); + + // set view port + gl.viewport(0, 0, filterArea.width, filterArea.height); + + projection.x = filterArea.width/2; + projection.y = -filterArea.height/2; + + offset.x = -filterArea.x; + offset.y = -filterArea.y; + + // update projection + // now restore the regular shader.. + // this.renderer.shaderManager.setShader(this.defaultShader); + //gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); + //gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y); + + gl.colorMask(true, true, true, true); + gl.clearColor(0,0,0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + + filterBlock._glFilterTexture = texture; + +}; + +/** + * Removes the last filter from the filter stack and doesn't return it. + * + */ +WebGLFilterManager.prototype.popFilter = function () { + var gl = this.renderer.gl; + + var filterBlock = this.filterStack.pop(); + var filterArea = filterBlock._filterArea; + var texture = filterBlock._glFilterTexture; + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + if (filterBlock.filterPasses.length > 1) { + gl.viewport(0, 0, filterArea.width, filterArea.height); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = 0; + this.vertexArray[1] = filterArea.height; + + this.vertexArray[2] = filterArea.width; + this.vertexArray[3] = filterArea.height; + + this.vertexArray[4] = 0; + this.vertexArray[5] = 0; + + this.vertexArray[6] = filterArea.width; + this.vertexArray[7] = 0; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + // now set the uvs.. + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + var inputTexture = texture; + var outputTexture = this.texturePool.pop(); + if (!outputTexture) { + outputTexture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + outputTexture.resize(this.width, this.height); + + // need to clear this FBO as it may have some left over elements from a previous filter. + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + gl.clear(gl.COLOR_BUFFER_BIT); + + gl.disable(gl.BLEND); + + for (var i = 0; i < filterBlock.filterPasses.length-1; i++) { + var filterPass = filterBlock.filterPasses[i]; + + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); + + // draw texture.. + //filterPass.applyFilterPass(filterArea.width, filterArea.height); + this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height); + + // swap the textures.. + var temp = inputTexture; + inputTexture = outputTexture; + outputTexture = temp; + } + + gl.enable(gl.BLEND); + + texture = inputTexture; + this.texturePool.push(outputTexture); + } + + var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1]; + + this.offsetX -= filterArea.x; + this.offsetY -= filterArea.y; + + var sizeX = this.width; + var sizeY = this.height; + + var offsetX = 0; + var offsetY = 0; + + var buffer = this.buffer; + + // time to render the filters texture to the previous scene + if (this.filterStack.length === 0) { + gl.colorMask(true, true, true, true);//this.transparent); + } + else { + var currentFilter = this.filterStack[this.filterStack.length-1]; + filterArea = currentFilter._filterArea; + + sizeX = filterArea.width; + sizeY = filterArea.height; + + offsetX = filterArea.x; + offsetY = filterArea.y; + + buffer = currentFilter._glFilterTexture.frameBuffer; + } + + // TODO need to remove these global elements.. + projection.x = sizeX/2; + projection.y = -sizeY/2; + + offset.x = offsetX; + offset.y = offsetY; + + filterArea = filterBlock._filterArea; + + var x = filterArea.x-offsetX; + var y = filterArea.y-offsetY; + + // update the buffers.. + // make sure to flip the y! + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = x; + this.vertexArray[1] = y + filterArea.height; + + this.vertexArray[2] = x + filterArea.width; + this.vertexArray[3] = y + filterArea.height; + + this.vertexArray[4] = x; + this.vertexArray[5] = y; + + this.vertexArray[6] = x + filterArea.width; + this.vertexArray[7] = y; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + gl.viewport(0, 0, sizeX, sizeY); + + // bind the buffer + gl.bindFramebuffer(gl.FRAMEBUFFER, buffer ); + + // set the blend mode! + //gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + // apply! + this.applyFilterPass(filter, filterArea, sizeX, sizeY); + + // now restore the regular shader.. should happen automatically now.. + // this.renderer.shaderManager.setShader(this.defaultShader); + // gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2); + // gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY); + + // return the texture to the pool + this.texturePool.push(texture); + filterBlock._glFilterTexture = null; +}; + + +/** + * Applies the filter to the specified area. + * + * @param filter {AbstractFilter} the filter that needs to be applied + * @param filterArea {Texture} TODO - might need an update + * @param width {number} the horizontal range of the filter + * @param height {number} the vertical range of the filter + */ +WebGLFilterManager.prototype.applyFilterPass = function (filter, filterArea, width, height) { + // use program + var gl = this.renderer.gl; + + var shader = filter.shaders[gl.id]; + + if (!shader) { + shader = new PixiShader(gl); + + shader.fragmentSrc = filter.fragmentSrc; + shader.uniforms = filter.uniforms; + shader.init(); + + filter.shaders[gl.id] = shader; + } + + // set the shader + this.renderer.shaderManager.setShader(shader); + +// gl.useProgram(shader.program); + + gl.uniform2f(shader.projectionVector, width/2, -height/2); + gl.uniform2f(shader.offsetVector, 0,0); + + if (filter.uniforms.dimensions) { + filter.uniforms.dimensions.value[0] = this.width;//width; + filter.uniforms.dimensions.value[1] = this.height;//height; + filter.uniforms.dimensions.value[2] = this.vertexArray[0]; + filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; + } + + shader.syncUniforms(); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + + // draw the filter... + gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); + + this.renderer.drawCount++; +}; + +/** + * Initialises the shader buffers. + * + */ +WebGLFilterManager.prototype.initShaderBuffers = function () { + var gl = this.renderer.gl; + + // create some buffers + this.vertexBuffer = gl.createBuffer(); + this.uvBuffer = gl.createBuffer(); + this.colorBuffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + + // bind and upload the vertexs.. + // keep a reference to the vertexFloatData.. + this.vertexArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW); + + // bind and upload the uv buffer + this.uvArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.uvArray, gl.STATIC_DRAW); + + this.colorArray = new Float32Array([1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.colorArray, gl.STATIC_DRAW); + + // bind and upload the index + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW); + +}; + +/** + * Destroys the filter and removes it from the filter stack. + * + */ +WebGLFilterManager.prototype.destroy = function () { + var gl = this.renderer.gl; + + this.filterStack = null; + + this.offsetX = 0; + this.offsetY = 0; + + // destroy textures + for (var i = 0; i < this.texturePool.length; i++) { + this.texturePool[i].destroy(); + } + + this.texturePool = null; + + //destroy buffers.. + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.uvBuffer); + gl.deleteBuffer(this.colorBuffer); + gl.deleteBuffer(this.indexBuffer); + + this.renderer = null; +}; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js new file mode 100644 index 0000000..9d47b5c --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -0,0 +1,20 @@ +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLManager(renderer) { + /** + * The renderer this manager works for. + * + * @member {WebGLRenderer} + */ + this.renderer = renderer; +} + +WebGLManager.prototype.constructor = WebGLManager; +module.exports = WebGLManager; + +WebGLManager.prototype.destroy = function () { + this.renderer = null; +}; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js new file mode 100644 index 0000000..6d46d85 --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -0,0 +1,41 @@ +var WebGLManager = require('./WebGLManager'), + WebGLGraphics = require('./WebGLGraphics'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLMaskManager(renderer) { + WebGLManager.call(this, renderer); +} + +WebGLMaskManager.prototype = Object.create(WebGLManager.prototype); +WebGLMaskManager.prototype.constructor = WebGLMaskManager; +module.exports = WebGLMaskManager; + +/** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {any[]} + */ +WebGLMaskManager.prototype.pushMask = function (maskData) { + if (maskData.dirty) { + WebGLGraphics.updateGraphics(maskData, this.renderer.gl); + } + + if (!maskData._webGL[this.renderer.gl.id].data.length) { + return; + } + + this.renderer.stencilManager.pushStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); +}; + +/** + * Removes the last filter from the filter stack and doesn't return it. + * + * @param maskData {any[]} + */ +WebGLMaskManager.prototype.popMask = function (maskData) { + this.renderer.stencilManager.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 new file mode 100644 index 0000000..d02d95d --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -0,0 +1,172 @@ +var WebGLManager = require('./WebGLManager'), + PrimitiveShader = require('../shaders/PrimitiveShader'), + ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), + PixiShader = require('../shaders/PixiShader'), + PixiFastShader = require('../shaders/PixiFastShader'), + StripShader = require('../shaders/StripShader'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLShaderManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {number} + */ + this.maxAttibs = 10; + + /** + * @member {any[]} + */ + this.attribState = []; + + /** + * @member {any[]} + */ + this.tempAttribState = []; + + for (var i = 0; i < this.maxAttibs; i++) { + this.attribState[i] = false; + } + + /** + * @member {any[]} + */ + this.stack = []; + + /** + * @member {number} + * @private + */ + this._currentId = -1; + + /** + * @member {Shader} + * @private + */ + 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; + + // listen for context and update necessary shaders + var self = this; + this.renderer.on('context', function (gl) { + // 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 PixiShader(gl); + + // this shader is used for the fast sprite rendering + self.fastShader = new PixiFastShader(gl); + + // the next one is used for rendering triangle strips + self.stripShader = new StripShader(gl); + + self.setShader(self.defaultShader); + }); +} + +WebGLShaderManager.prototype = Object.create(WebGLManager.prototype); +WebGLShaderManager.prototype.constructor = WebGLShaderManager; +module.exports = WebGLShaderManager; + +/** + * Takes the attributes given in parameters. + * + * @param attribs {Array} attribs + */ +WebGLShaderManager.prototype.setAttribs = function (attribs) { + // reset temp state + var i; + + for (i = 0; i < this.tempAttribState.length; i++) { + this.tempAttribState[i] = false; + } + + // set the new attribs + for (var a in attribs) { + this.tempAttribState[attribs[a]] = true; + } + + var gl = this.renderer.gl; + + for (i = 0; i < this.attribState.length; i++) { + if (this.attribState[i] !== this.tempAttribState[i]) { + this.attribState[i] = this.tempAttribState[i]; + + if (this.attribState[i]) { + gl.enableVertexAttribArray(i); + } + else { + gl.disableVertexAttribArray(i); + } + } + } +}; + +/** + * Sets the current shader. + * + * @param shader {Any} + */ +WebGLShaderManager.prototype.setShader = function (shader) { + if (this._currentId === shader.uuid) { + return false; + } + + this._currentId = shader.uuid; + + this.currentShader = shader; + + this.renderer.gl.useProgram(shader.program); + this.setAttribs(shader.attributes); + + return true; +}; + +/** + * Destroys this object. + * + */ +WebGLShaderManager.prototype.destroy = function () { + 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/managers/WebGLStencilManager.js b/src/core/renderers/webgl/managers/WebGLStencilManager.js new file mode 100644 index 0000000..0bedae6 --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLStencilManager.js @@ -0,0 +1,256 @@ +var WebGLManager = require('./WebGLManager'), + utils = require('../../../utils'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLStencilManager(renderer) { + WebGLManager.call(this, renderer); + + this.stencilStack = []; + this.reverse = true; + this.count = 0; +} + +WebGLStencilManager.prototype = Object.create(WebGLManager.prototype); +WebGLStencilManager.prototype.constructor = WebGLStencilManager; +module.exports = WebGLStencilManager; + +/** + * Applies the Mask and adds it to the current filter stack. + * + * @param graphics {Graphics} + * @param webGLData {any[]} + */ +WebGLStencilManager.prototype.pushStencil = function (graphics, webGLData) { + var gl = this.renderer.gl; + + this.bindGraphics(graphics, webGLData, this.renderer); + + if (this.stencilStack.length === 0) { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + this.reverse = true; + this.count = 0; + } + + this.stencilStack.push(webGLData); + + var level = this.count; + + gl.colorMask(false, false, false, false); + + gl.stencilFunc(gl.ALWAYS,0,0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); + + // draw the triangle strip! + + if (webGLData.mode === 1) { + gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + + // draw a quad to increment.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + } + + this.reverse = !this.reverse; + } + else { + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + } + } + + gl.colorMask(true, true, true, true); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + + this.count++; +}; + +/** + * TODO this does not belong here! + * + * @param graphics {Graphics} + * @param webGLData {Array} + */ +WebGLStencilManager.prototype.bindGraphics = function (graphics, webGLData) { + //if (this._currentGraphics === graphics)return; + this._currentGraphics = graphics; + + var gl = this.renderer.gl; + + // bind the graphics object.. + var projection = this.renderer.projection, + offset = this.renderer.offset, + shader;// = this.renderer.shaderManager.primitiveShader; + + if (webGLData.mode === 1) { + shader = this.renderer.shaderManager.complexPrimitiveShader; + + this.renderer.shaderManager.setShader(shader); + + gl.uniform1f(shader.flipY, this.renderer.flipY); + + gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); + + gl.uniform2f(shader.projectionVector, projection.x, -projection.y); + gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); + + gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); + gl.uniform3fv(shader.color, webGLData.color); + + gl.uniform1f(shader.alpha, graphics.worldAlpha * webGLData.alpha); + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 2, 0); + + + // now do the rest.. + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + } + else { + //this.renderer.shaderManager.activatePrimitiveShader(); + shader = this.renderer.shaderManager.primitiveShader; + this.renderer.shaderManager.setShader( shader ); + + gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); + + gl.uniform1f(shader.flipY, this.renderer.flipY); + gl.uniform2f(shader.projectionVector, projection.x, -projection.y); + gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); + + gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); + + gl.uniform1f(shader.alpha, graphics.worldAlpha); + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); + gl.vertexAttribPointer(shader.colorAttribute, 4, gl.FLOAT, false,4 * 6, 2 * 4); + + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + } +}; + +/** + * @param graphics {Graphics} + * @param webGLData {Array} + */ +WebGLStencilManager.prototype.popStencil = function (graphics, webGLData) { + var gl = this.renderer.gl; + + this.stencilStack.pop(); + + this.count--; + + if (this.stencilStack.length === 0) { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + + } + else { + + var level = this.count; + + this.bindGraphics(graphics, webGLData, this.renderer); + + gl.colorMask(false, false, false, false); + + if (webGLData.mode === 1) { + this.reverse = !this.reverse; + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + + // draw a quad to increment.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + gl.stencilFunc(gl.ALWAYS,0,0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); + + // draw the triangle strip! + gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + } + + } + else { + // console.log("<<>>") + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + } + } + + gl.colorMask(true, true, true, true); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + + + } +}; + +/** + * Destroys the mask stack. + * + */ +WebGLStencilManager.prototype.destroy = function () { + this.renderer = null; + this.stencilStack = null; +}; diff --git a/src/core/renderers/webgl/utils/WebGLBlendModeManager.js b/src/core/renderers/webgl/utils/WebGLBlendModeManager.js deleted file mode 100644 index 9f2f89a..0000000 --- a/src/core/renderers/webgl/utils/WebGLBlendModeManager.js +++ /dev/null @@ -1,37 +0,0 @@ -var WebGLManager = require('./WebGLManager'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLBlendModeManager(renderer) { - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -WebGLBlendModeManager.prototype = Object.create(WebGLManager.prototype); -WebGLBlendModeManager.prototype.constructor = WebGLBlendModeManager; -module.exports = WebGLBlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as BlendModes.ADD - */ -WebGLBlendModeManager.prototype.setBlendMode = function (blendMode) { - if (this.currentBlendMode === blendMode) { - return false; - } - - this.currentBlendMode = blendMode; - - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); - - return true; -}; diff --git a/src/core/renderers/webgl/utils/WebGLFilterManager.js b/src/core/renderers/webgl/utils/WebGLFilterManager.js deleted file mode 100644 index 176e7cd..0000000 --- a/src/core/renderers/webgl/utils/WebGLFilterManager.js +++ /dev/null @@ -1,446 +0,0 @@ -var WebGLManager = require('./WebGLManager'), - FilterTexture = require('./FilterTexture'), - PixiShader = require('../shaders/PixiShader'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLFilterManager(renderer) { - WebGLManager.call(this, renderer); - - /** - * @member {any[]} - */ - this.filterStack = []; - - /** - * @member {any[]]} - */ - this.texturePool = []; - - /** - * @member {number} - */ - this.offsetX = 0; - - /** - * @member {number} - */ - this.offsetY = 0; - - // listen for context and update necessary buffers - var self = this; - this.renderer.on('context', function () { - self.texturePool.length = 0; - self.initShaderBuffers(); - }); -} - -WebGLFilterManager.prototype = Object.create(WebGLManager.prototype); -WebGLFilterManager.prototype.constructor = WebGLFilterManager; -module.exports = WebGLFilterManager; - -/** - * @param renderer {WebGLRenderer} - * @param buffer {ArrayBuffer} - */ -WebGLFilterManager.prototype.begin = function (renderer, buffer) { - this.renderer = renderer; - this.defaultShader = renderer.shaderManager.defaultShader; - - this.width = renderer.projection.x * 2; - this.height = -renderer.projection.y * 2; - - this.buffer = buffer; -}; - -/** - * Applies the filter and adds it to the current filter stack. - * - * @param filterBlock {object} the filter that will be pushed to the current filter stack - */ -WebGLFilterManager.prototype.pushFilter = function (filterBlock) { - var gl = this.renderer.gl; - - var projection = this.renderer.projection; - var offset = this.renderer.offset; - - filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds(); - - // filter program - // OPTIMISATION - the first filter is free if its a simple color change? - this.filterStack.push(filterBlock); - - var filter = filterBlock.filterPasses[0]; - - this.offsetX += filterBlock._filterArea.x; - this.offsetY += filterBlock._filterArea.y; - - var texture = this.texturePool.pop(); - if (!texture) { - texture = new FilterTexture(this.renderer.gl, this.width, this.height); - } - else { - texture.resize(this.width, this.height); - } - - gl.bindTexture(gl.TEXTURE_2D, texture.texture); - - var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea; - - var padding = filter.padding; - filterArea.x -= padding; - filterArea.y -= padding; - filterArea.width += padding * 2; - filterArea.height += padding * 2; - - // cap filter to screen size.. - if (filterArea.x < 0) { - filterArea.x = 0; - } - - if (filterArea.width > this.width) { - filterArea.width = this.width; - } - - if (filterArea.y < 0) { - filterArea.y = 0; - } - - if (filterArea.height > this.height) { - filterArea.height = this.height; - } - - //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); - - // set view port - gl.viewport(0, 0, filterArea.width, filterArea.height); - - projection.x = filterArea.width/2; - projection.y = -filterArea.height/2; - - offset.x = -filterArea.x; - offset.y = -filterArea.y; - - // update projection - // now restore the regular shader.. - // this.renderer.shaderManager.setShader(this.defaultShader); - //gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); - //gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y); - - gl.colorMask(true, true, true, true); - gl.clearColor(0,0,0, 0); - gl.clear(gl.COLOR_BUFFER_BIT); - - filterBlock._glFilterTexture = texture; - -}; - -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -WebGLFilterManager.prototype.popFilter = function () { - var gl = this.renderer.gl; - - var filterBlock = this.filterStack.pop(); - var filterArea = filterBlock._filterArea; - var texture = filterBlock._glFilterTexture; - var projection = this.renderer.projection; - var offset = this.renderer.offset; - - if (filterBlock.filterPasses.length > 1) { - gl.viewport(0, 0, filterArea.width, filterArea.height); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - - this.vertexArray[0] = 0; - this.vertexArray[1] = filterArea.height; - - this.vertexArray[2] = filterArea.width; - this.vertexArray[3] = filterArea.height; - - this.vertexArray[4] = 0; - this.vertexArray[5] = 0; - - this.vertexArray[6] = filterArea.width; - this.vertexArray[7] = 0; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - // now set the uvs.. - this.uvArray[2] = filterArea.width/this.width; - this.uvArray[5] = filterArea.height/this.height; - this.uvArray[6] = filterArea.width/this.width; - this.uvArray[7] = filterArea.height/this.height; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); - - var inputTexture = texture; - var outputTexture = this.texturePool.pop(); - if (!outputTexture) { - outputTexture = new FilterTexture(this.renderer.gl, this.width, this.height); - } - outputTexture.resize(this.width, this.height); - - // need to clear this FBO as it may have some left over elements from a previous filter. - gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); - gl.clear(gl.COLOR_BUFFER_BIT); - - gl.disable(gl.BLEND); - - for (var i = 0; i < filterBlock.filterPasses.length-1; i++) { - var filterPass = filterBlock.filterPasses[i]; - - gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); - - // set texture - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); - - // draw texture.. - //filterPass.applyFilterPass(filterArea.width, filterArea.height); - this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height); - - // swap the textures.. - var temp = inputTexture; - inputTexture = outputTexture; - outputTexture = temp; - } - - gl.enable(gl.BLEND); - - texture = inputTexture; - this.texturePool.push(outputTexture); - } - - var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1]; - - this.offsetX -= filterArea.x; - this.offsetY -= filterArea.y; - - var sizeX = this.width; - var sizeY = this.height; - - var offsetX = 0; - var offsetY = 0; - - var buffer = this.buffer; - - // time to render the filters texture to the previous scene - if (this.filterStack.length === 0) { - gl.colorMask(true, true, true, true);//this.transparent); - } - else { - var currentFilter = this.filterStack[this.filterStack.length-1]; - filterArea = currentFilter._filterArea; - - sizeX = filterArea.width; - sizeY = filterArea.height; - - offsetX = filterArea.x; - offsetY = filterArea.y; - - buffer = currentFilter._glFilterTexture.frameBuffer; - } - - // TODO need to remove these global elements.. - projection.x = sizeX/2; - projection.y = -sizeY/2; - - offset.x = offsetX; - offset.y = offsetY; - - filterArea = filterBlock._filterArea; - - var x = filterArea.x-offsetX; - var y = filterArea.y-offsetY; - - // update the buffers.. - // make sure to flip the y! - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - - this.vertexArray[0] = x; - this.vertexArray[1] = y + filterArea.height; - - this.vertexArray[2] = x + filterArea.width; - this.vertexArray[3] = y + filterArea.height; - - this.vertexArray[4] = x; - this.vertexArray[5] = y; - - this.vertexArray[6] = x + filterArea.width; - this.vertexArray[7] = y; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - - this.uvArray[2] = filterArea.width/this.width; - this.uvArray[5] = filterArea.height/this.height; - this.uvArray[6] = filterArea.width/this.width; - this.uvArray[7] = filterArea.height/this.height; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); - - gl.viewport(0, 0, sizeX, sizeY); - - // bind the buffer - gl.bindFramebuffer(gl.FRAMEBUFFER, buffer ); - - // set the blend mode! - //gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) - - // set texture - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, texture.texture); - - // apply! - this.applyFilterPass(filter, filterArea, sizeX, sizeY); - - // now restore the regular shader.. should happen automatically now.. - // this.renderer.shaderManager.setShader(this.defaultShader); - // gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2); - // gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY); - - // return the texture to the pool - this.texturePool.push(texture); - filterBlock._glFilterTexture = null; -}; - - -/** - * Applies the filter to the specified area. - * - * @param filter {AbstractFilter} the filter that needs to be applied - * @param filterArea {Texture} TODO - might need an update - * @param width {number} the horizontal range of the filter - * @param height {number} the vertical range of the filter - */ -WebGLFilterManager.prototype.applyFilterPass = function (filter, filterArea, width, height) { - // use program - var gl = this.renderer.gl; - - var shader = filter.shaders[gl.id]; - - if (!shader) { - shader = new PixiShader(gl); - - shader.fragmentSrc = filter.fragmentSrc; - shader.uniforms = filter.uniforms; - shader.init(); - - filter.shaders[gl.id] = shader; - } - - // set the shader - this.renderer.shaderManager.setShader(shader); - -// gl.useProgram(shader.program); - - gl.uniform2f(shader.projectionVector, width/2, -height/2); - gl.uniform2f(shader.offsetVector, 0,0); - - if (filter.uniforms.dimensions) { - filter.uniforms.dimensions.value[0] = this.width;//width; - filter.uniforms.dimensions.value[1] = this.height;//height; - filter.uniforms.dimensions.value[2] = this.vertexArray[0]; - filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; - } - - shader.syncUniforms(); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); - gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - - // draw the filter... - gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); - - this.renderer.drawCount++; -}; - -/** - * Initialises the shader buffers. - * - */ -WebGLFilterManager.prototype.initShaderBuffers = function () { - var gl = this.renderer.gl; - - // create some buffers - this.vertexBuffer = gl.createBuffer(); - this.uvBuffer = gl.createBuffer(); - this.colorBuffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - - // bind and upload the vertexs.. - // keep a reference to the vertexFloatData.. - this.vertexArray = new Float32Array([0.0, 0.0, - 1.0, 0.0, - 0.0, 1.0, - 1.0, 1.0]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW); - - // bind and upload the uv buffer - this.uvArray = new Float32Array([0.0, 0.0, - 1.0, 0.0, - 0.0, 1.0, - 1.0, 1.0]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.uvArray, gl.STATIC_DRAW); - - this.colorArray = new Float32Array([1.0, 0xFFFFFF, - 1.0, 0xFFFFFF, - 1.0, 0xFFFFFF, - 1.0, 0xFFFFFF]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.colorArray, gl.STATIC_DRAW); - - // bind and upload the index - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW); - -}; - -/** - * Destroys the filter and removes it from the filter stack. - * - */ -WebGLFilterManager.prototype.destroy = function () { - var gl = this.renderer.gl; - - this.filterStack = null; - - this.offsetX = 0; - this.offsetY = 0; - - // destroy textures - for (var i = 0; i < this.texturePool.length; i++) { - this.texturePool[i].destroy(); - } - - this.texturePool = null; - - //destroy buffers.. - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.uvBuffer); - gl.deleteBuffer(this.colorBuffer); - gl.deleteBuffer(this.indexBuffer); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/WebGLGraphics.js b/src/core/renderers/webgl/utils/WebGLGraphics.js index 09f540f..1d1fe40 100644 --- a/src/core/renderers/webgl/utils/WebGLGraphics.js +++ b/src/core/renderers/webgl/utils/WebGLGraphics.js @@ -1,6 +1,5 @@ var utils = require('../../../utils'), math = require('../../../math'), - Graphics = require('../../../../primitives/Graphics'), WebGLGraphicsData = require('./WebGLGraphicsData'); /** @@ -122,7 +121,7 @@ for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) { var data = graphics.graphicsData[i]; - if (data.type === Graphics.POLY) { + 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) { @@ -164,13 +163,13 @@ else { webGLData = WebGLGraphics.switchMode(webGL, 0); - if (data.type === Graphics.RECT) { + if (data.type === CONST.SHAPES.RECT) { WebGLGraphics.buildRectangle(data, webGLData); } - else if (data.type === Graphics.CIRC || data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) { WebGLGraphics.buildCircle(data, webGLData); } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { WebGLGraphics.buildRoundedRectangle(data, webGLData); } } @@ -415,7 +414,7 @@ var height; // TODO - bit hacky?? - if (graphicsData.type === Graphics.CIRC) { + if (graphicsData.type === CONST.SHAPES.CIRC) { width = circleData.radius; height = circleData.radius; } diff --git a/src/core/renderers/webgl/utils/WebGLManager.js b/src/core/renderers/webgl/utils/WebGLManager.js deleted file mode 100644 index 9d47b5c..0000000 --- a/src/core/renderers/webgl/utils/WebGLManager.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLManager(renderer) { - /** - * The renderer this manager works for. - * - * @member {WebGLRenderer} - */ - this.renderer = renderer; -} - -WebGLManager.prototype.constructor = WebGLManager; -module.exports = WebGLManager; - -WebGLManager.prototype.destroy = function () { - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/WebGLMaskManager.js b/src/core/renderers/webgl/utils/WebGLMaskManager.js deleted file mode 100644 index 6d46d85..0000000 --- a/src/core/renderers/webgl/utils/WebGLMaskManager.js +++ /dev/null @@ -1,41 +0,0 @@ -var WebGLManager = require('./WebGLManager'), - WebGLGraphics = require('./WebGLGraphics'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLMaskManager(renderer) { - WebGLManager.call(this, renderer); -} - -WebGLMaskManager.prototype = Object.create(WebGLManager.prototype); -WebGLMaskManager.prototype.constructor = WebGLMaskManager; -module.exports = WebGLMaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {any[]} - */ -WebGLMaskManager.prototype.pushMask = function (maskData) { - if (maskData.dirty) { - WebGLGraphics.updateGraphics(maskData, this.renderer.gl); - } - - if (!maskData._webGL[this.renderer.gl.id].data.length) { - return; - } - - this.renderer.stencilManager.pushStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); -}; - -/** - * Removes the last filter from the filter stack and doesn't return it. - * - * @param maskData {any[]} - */ -WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.stencilManager.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); -}; diff --git a/src/core/renderers/webgl/utils/WebGLShaderManager.js b/src/core/renderers/webgl/utils/WebGLShaderManager.js deleted file mode 100644 index d02d95d..0000000 --- a/src/core/renderers/webgl/utils/WebGLShaderManager.js +++ /dev/null @@ -1,172 +0,0 @@ -var WebGLManager = require('./WebGLManager'), - PrimitiveShader = require('../shaders/PrimitiveShader'), - ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), - PixiShader = require('../shaders/PixiShader'), - PixiFastShader = require('../shaders/PixiFastShader'), - StripShader = require('../shaders/StripShader'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLShaderManager(renderer) { - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.maxAttibs = 10; - - /** - * @member {any[]} - */ - this.attribState = []; - - /** - * @member {any[]} - */ - this.tempAttribState = []; - - for (var i = 0; i < this.maxAttibs; i++) { - this.attribState[i] = false; - } - - /** - * @member {any[]} - */ - this.stack = []; - - /** - * @member {number} - * @private - */ - this._currentId = -1; - - /** - * @member {Shader} - * @private - */ - 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; - - // listen for context and update necessary shaders - var self = this; - this.renderer.on('context', function (gl) { - // 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 PixiShader(gl); - - // this shader is used for the fast sprite rendering - self.fastShader = new PixiFastShader(gl); - - // the next one is used for rendering triangle strips - self.stripShader = new StripShader(gl); - - self.setShader(self.defaultShader); - }); -} - -WebGLShaderManager.prototype = Object.create(WebGLManager.prototype); -WebGLShaderManager.prototype.constructor = WebGLShaderManager; -module.exports = WebGLShaderManager; - -/** - * Takes the attributes given in parameters. - * - * @param attribs {Array} attribs - */ -WebGLShaderManager.prototype.setAttribs = function (attribs) { - // reset temp state - var i; - - for (i = 0; i < this.tempAttribState.length; i++) { - this.tempAttribState[i] = false; - } - - // set the new attribs - for (var a in attribs) { - this.tempAttribState[attribs[a]] = true; - } - - var gl = this.renderer.gl; - - for (i = 0; i < this.attribState.length; i++) { - if (this.attribState[i] !== this.tempAttribState[i]) { - this.attribState[i] = this.tempAttribState[i]; - - if (this.attribState[i]) { - gl.enableVertexAttribArray(i); - } - else { - gl.disableVertexAttribArray(i); - } - } - } -}; - -/** - * Sets the current shader. - * - * @param shader {Any} - */ -WebGLShaderManager.prototype.setShader = function (shader) { - if (this._currentId === shader.uuid) { - return false; - } - - this._currentId = shader.uuid; - - this.currentShader = shader; - - this.renderer.gl.useProgram(shader.program); - this.setAttribs(shader.attributes); - - return true; -}; - -/** - * Destroys this object. - * - */ -WebGLShaderManager.prototype.destroy = function () { - 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/utils/WebGLStencilManager.js b/src/core/renderers/webgl/utils/WebGLStencilManager.js deleted file mode 100644 index 0bedae6..0000000 --- a/src/core/renderers/webgl/utils/WebGLStencilManager.js +++ /dev/null @@ -1,256 +0,0 @@ -var WebGLManager = require('./WebGLManager'), - utils = require('../../../utils'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLStencilManager(renderer) { - WebGLManager.call(this, renderer); - - this.stencilStack = []; - this.reverse = true; - this.count = 0; -} - -WebGLStencilManager.prototype = Object.create(WebGLManager.prototype); -WebGLStencilManager.prototype.constructor = WebGLStencilManager; -module.exports = WebGLStencilManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param graphics {Graphics} - * @param webGLData {any[]} - */ -WebGLStencilManager.prototype.pushStencil = function (graphics, webGLData) { - var gl = this.renderer.gl; - - this.bindGraphics(graphics, webGLData, this.renderer); - - if (this.stencilStack.length === 0) { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - this.reverse = true; - this.count = 0; - } - - this.stencilStack.push(webGLData); - - var level = this.count; - - gl.colorMask(false, false, false, false); - - gl.stencilFunc(gl.ALWAYS,0,0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); - - // draw the triangle strip! - - if (webGLData.mode === 1) { - gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); - - if (this.reverse) { - gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); - } - else { - gl.stencilFunc(gl.EQUAL,level, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - } - - // draw a quad to increment.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - if (this.reverse) { - gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); - } - else { - gl.stencilFunc(gl.EQUAL,level+1, 0xFF); - } - - this.reverse = !this.reverse; - } - else { - if (!this.reverse) { - gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); - } - else { - gl.stencilFunc(gl.EQUAL,level, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - } - - gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); - - if (!this.reverse) { - gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); - } - else { - gl.stencilFunc(gl.EQUAL,level+1, 0xFF); - } - } - - gl.colorMask(true, true, true, true); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); - - this.count++; -}; - -/** - * TODO this does not belong here! - * - * @param graphics {Graphics} - * @param webGLData {Array} - */ -WebGLStencilManager.prototype.bindGraphics = function (graphics, webGLData) { - //if (this._currentGraphics === graphics)return; - this._currentGraphics = graphics; - - var gl = this.renderer.gl; - - // bind the graphics object.. - var projection = this.renderer.projection, - offset = this.renderer.offset, - shader;// = this.renderer.shaderManager.primitiveShader; - - if (webGLData.mode === 1) { - shader = this.renderer.shaderManager.complexPrimitiveShader; - - this.renderer.shaderManager.setShader(shader); - - gl.uniform1f(shader.flipY, this.renderer.flipY); - - gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); - - gl.uniform2f(shader.projectionVector, projection.x, -projection.y); - gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); - - gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); - gl.uniform3fv(shader.color, webGLData.color); - - gl.uniform1f(shader.alpha, graphics.worldAlpha * webGLData.alpha); - - gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); - - gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 2, 0); - - - // now do the rest.. - // set the index buffer! - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); - } - else { - //this.renderer.shaderManager.activatePrimitiveShader(); - shader = this.renderer.shaderManager.primitiveShader; - this.renderer.shaderManager.setShader( shader ); - - gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); - - gl.uniform1f(shader.flipY, this.renderer.flipY); - gl.uniform2f(shader.projectionVector, projection.x, -projection.y); - gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); - - gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); - - gl.uniform1f(shader.alpha, graphics.worldAlpha); - - gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); - - gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); - gl.vertexAttribPointer(shader.colorAttribute, 4, gl.FLOAT, false,4 * 6, 2 * 4); - - // set the index buffer! - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); - } -}; - -/** - * @param graphics {Graphics} - * @param webGLData {Array} - */ -WebGLStencilManager.prototype.popStencil = function (graphics, webGLData) { - var gl = this.renderer.gl; - - this.stencilStack.pop(); - - this.count--; - - if (this.stencilStack.length === 0) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - - } - else { - - var level = this.count; - - this.bindGraphics(graphics, webGLData, this.renderer); - - gl.colorMask(false, false, false, false); - - if (webGLData.mode === 1) { - this.reverse = !this.reverse; - - if (this.reverse) { - gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - } - else { - gl.stencilFunc(gl.EQUAL,level+1, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); - } - - // draw a quad to increment.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - gl.stencilFunc(gl.ALWAYS,0,0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); - - // draw the triangle strip! - gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); - - if (!this.reverse) { - gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); - } - else { - gl.stencilFunc(gl.EQUAL,level, 0xFF); - } - - } - else { - // console.log("<<>>") - if (!this.reverse) { - gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - } - else { - gl.stencilFunc(gl.EQUAL,level+1, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); - } - - gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); - - if (!this.reverse) { - gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); - } - else { - gl.stencilFunc(gl.EQUAL,level, 0xFF); - } - } - - gl.colorMask(true, true, true, true); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); - - - } -}; - -/** - * Destroys the mask stack. - * - */ -WebGLStencilManager.prototype.destroy = function () { - this.renderer = null; - this.stencilStack = null; -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js new file mode 100644 index 0000000..b992c31 --- /dev/null +++ b/src/core/textures/VideoBaseTexture.js @@ -0,0 +1,138 @@ +var BaseTexture = require('./BaseTexture'), + utils = require('../utils'); + +/** + * A texture of a [playing] Video. + * + * See the ["deus" demo](http://www.goodboydigital.com/pixijs/examples/deus/). + * + * @class + * @extends BaseTexture + * @namespace PIXI + * @param source {HTMLVideoElement} + * @param [scaleMode] {number} See {@link scaleModes} for possible values + */ +function VideoBaseTexture(source, scaleMode) { + if (!source){ + throw new Error('No video source element specified.'); + } + + // hook in here to check if video is already available. + // BaseTexture looks for a source.complete boolean, plus width & height. + + if ((source.readyState === source.HAVE_ENOUGH_DATA || source.readyState === source.HAVE_FUTURE_DATA) && source.width && source.height) { + source.complete = true; + } + + BaseTexture.call(this, source, scaleMode); + + this.autoUpdate = false; + + this._boundOnUpdate = this._onUpdate.bind(this); + this._boundOnCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) { + source.addEventListener('canplay', this._boundOnCanPlay); + source.addEventListener('canplaythrough', this._boundOnCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } +} + +VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); +VideoBaseTexture.prototype.constructor = VideoBaseTexture; +module.exports = VideoBaseTexture; + +VideoBaseTexture.prototype._onUpdate = function () { + if (this.autoUpdate) { + window.requestAnimationFrame(this._boundOnUpdate); + this.needsUpdate = true; + } +}; + +VideoBaseTexture.prototype._onPlayStart = function () { + if (!this.autoUpdate) { + window.requestAnimationFrame(this._boundOnUpdate); + this.autoUpdate = true; + } +}; + +VideoBaseTexture.prototype._onPlayStop = function () { + this.autoUpdate = false; +}; + +VideoBaseTexture.prototype._onCanPlay = function () { + if (event.type === 'canplaythrough') { + this.hasLoaded = true; + + + if (this.source) { + this.source.removeEventListener('canplay', this._boundOnCanPlay); + this.source.removeEventListener('canplaythrough', this._boundOnCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + // prevent multiple loaded dispatches.. + if (!this.__loaded){ + this.__loaded = true; + this.dispatchEvent({ type: 'loaded', content: this }); + } + } + } +}; + +VideoBaseTexture.prototype.destroy = function () { + if (this.source && this.source._pixiId) { + utils.BaseTextureCache[ this.source._pixiId ] = null; + delete utils.BaseTextureCache[ this.source._pixiId ]; + + this.source._pixiId = null; + delete this.source._pixiId; + } + + BaseTexture.prototype.destroy.call(this); +}; + +/** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} + * @param scaleMode {number} See {@link scaleModes} for possible values + * @return {VideoBaseTexture} + */ +VideoBaseTexture.fromVideo = function (video, scaleMode) { + if (!video._pixiId) { + video._pixiId = 'video_' + utils.uuid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; +}; + +/** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param videoSrc {string} The URL for the video. + * @param scaleMode {number} See {@link scaleModes} for possible values + * @return {VideoBaseTexture} + */ +VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) { + var video = document.createElement('video'); + + video.src = videoSrc; + video.autoPlay = true; + video.play(); + + return VideoBaseTexture.textureFromVideo(video, scaleMode); +}; diff --git a/src/primitives/Graphics.js b/src/primitives/Graphics.js deleted file mode 100644 index 7283ec5..0000000 --- a/src/primitives/Graphics.js +++ /dev/null @@ -1,1067 +0,0 @@ -var core = require('../core'), - GraphicsData = require('./GraphicsData'); - -/** - * 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() { - core.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 {object[]} - * @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 = core.CONST.blendModes.NORMAL; - - /** - * Current path - * - * @member {object} - * @private - */ - this.currentPath = null; - - /** - * Array containing some WebGL-related properties used by the WebGL renderer. - * - * @member {object[]} - * @private - */ - 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 core.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(core.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; - } - } - } -}); - -/** - * 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 core.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 core.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; - - 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 core.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 core.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 core.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 core.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 core.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 core.CanvasBuffer(bounds.width * resolution, bounds.height * resolution); - - var texture = core.Texture.fromCanvas(canvasBuffer.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - canvasBuffer.context.scale(resolution, resolution); - - canvasBuffer.context.translate(-bounds.x,-bounds.y); - - core.CanvasGraphics.renderGraphics(this, canvasBuffer.context); - - return texture; -}; - -/** - * Renders the object using the WebGL renderer - * - * @method _renderWebGL - * @param renderSession {RenderSession} - * @private - */ -Graphics.prototype._renderWebGL = function (renderSession) { - // 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 on the gpu too! - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.worldAlpha = this.worldAlpha; - - core.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); - - return; - } - else { - renderSession.spriteBatch.stop(); - renderSession.blendModeManager.setBlendMode(this.blendMode); - - if (this._mask) { - renderSession.maskManager.pushMask(this._mask, renderSession); - } - - if (this._filters) { - renderSession.filterManager.pushFilter(this._filterBlock); - } - - // check blend mode - if (this.blendMode !== renderSession.spriteBatch.currentBlendMode) { - renderSession.spriteBatch.currentBlendMode = this.blendMode; - - var blendModeWebGL = blendModesWebGL[renderSession.spriteBatch.currentBlendMode]; - - renderSession.spriteBatch.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); - } - - // check if the webgl graphic needs to be updated - if (this.glDirty) { - this.dirty = true; - this.glDirty = false; - } - - core.WebGLGraphics.renderGraphics(this, renderSession); - - // only render if it has children! - if (this.children.length) { - renderSession.spriteBatch.start(); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) { - this.children[i]._renderWebGL(renderSession); - } - - renderSession.spriteBatch.stop(); - } - - if (this._filters) { - renderSession.filterManager.popFilter(); - } - - if (this._mask) { - renderSession.maskManager.popMask(this.mask, renderSession); - } - - renderSession.drawCount++; - - renderSession.spriteBatch.start(); - } -}; - -/** - * Renders the object using the Canvas renderer - * - * @method _renderCanvas - * @param renderSession {RenderSession} - * @private - */ -Graphics.prototype._renderCanvas = function (renderSession) { - // 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; - - core.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession); - - return; - } - else { - var context = renderSession.context; - var transform = this.worldTransform; - - if (this.blendMode !== renderSession.currentBlendMode) { - renderSession.currentBlendMode = this.blendMode; - context.globalCompositeOperation = blendModesCanvas[renderSession.currentBlendMode]; - } - - if (this._mask) { - renderSession.maskManager.pushMask(this._mask, renderSession); - } - - var resolution = renderSession.resolution; - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - core.CanvasGraphics.renderGraphics(this, context); - - for (var i = 0, j = this.children.length; i < j; ++i) { - this.children[i]._renderCanvas(renderSession); - } - - if (this._mask) { - renderSession.maskManager.popMask(renderSession); - } - } -}; - -/** - * 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 core.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 === Graphics.RECT || type === Graphics.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 === Graphics.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 === Graphics.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 core.CanvasBuffer(bounds.width, bounds.height); - var texture = core.Texture.fromCanvas(canvasBuffer.canvas); - - this._cachedSprite = new core.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.. - core.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 === Graphics.POLY) { - data.shape.closed = this.filling; - this.currentPath = data; - } - - this.dirty = true; - - return data; -}; - -/** - * @static - * @constant - */ -Graphics.POLY = 0; - -/** - * @static - * @constant - */ -Graphics.RECT = 1; - -/** - * @static - * @constant - */ -Graphics.CIRC = 2; - -/** - * @static - * @constant - */ -Graphics.ELIP = 3; - -/** - * @static - * @constant - */ -Graphics.RREC = 4; - -// REFACTOR: Move these to their classes, move types to central location. -core.math.Polygon.prototype.type = Graphics.POLY; -core.math.Rectangle.prototype.type = Graphics.RECT; -core.math.Circle.prototype.type = Graphics.CIRC; -core.math.Ellipse.prototype.type = Graphics.ELIP; -core.math.RoundedRectangle.prototype.type = Graphics.RREC; - diff --git a/src/primitives/GraphicsData.js b/src/primitives/GraphicsData.js deleted file mode 100644 index 9377ef7..0000000 --- a/src/primitives/GraphicsData.js +++ /dev/null @@ -1,23 +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; diff --git a/src/core/const.js b/src/core/const.js index 2803e5b..aea72ec 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -130,5 +130,25 @@ backgroundColor: 0x000000, clearBeforeRender: true, preserveDrawingBuffer: false + }, + + /** + * Constants that identify shapes, mainly to prevent `instanceof` calls. + * + * @static + * @constant + * @property {object} SHAPES + * @property {object} SHAPES.POLY=0 + * @property {object} SHAPES.RECT=1 + * @property {object} SHAPES.CIRC=2 + * @property {object} SHAPES.ELIP=3 + * @property {object} SHAPES.RREC=4 + */ + SHAPES: { + POLY = 0; + RECT = 1; + CIRC = 2; + ELIP = 3; + RREC = 4; } }; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index bfd59cf..7c0e7c6 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -95,7 +95,7 @@ this.filterArea = null; /** - * cached sin rotation and cos rotation + * cached sin rotation * * @member {number} * @private @@ -103,7 +103,7 @@ this._sr = 0; /** - * cached sin rotation and cos rotation + * cached cos rotation * * @member {number} * @private @@ -400,7 +400,7 @@ DisplayObject.prototype.generateTexture = function (resolution, scaleMode, renderer) { var bounds = this.getLocalBounds(); - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0, renderer, scaleMode, resolution); _tempMatrix.tx = -bounds.x; _tempMatrix.ty = -bounds.y; @@ -474,7 +474,7 @@ var bounds = this.getLocalBounds(); if (!this._cachedSprite) { - var renderTexture = new RenderTexture(bounds.width | 0, bounds.height | 0); //, renderer); + var renderTexture = new RenderTexture(renderer, bounds.width | 0, bounds.height | 0); this._cachedSprite = new Sprite(renderTexture); this._cachedSprite.worldTransform = this.worldTransform; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 3f6a3b6..69cfdc9 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Circle object can be used to specify a hit area for displayObjects @@ -29,10 +30,11 @@ this.radius = radius || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.CIRC in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.CIRC; } Circle.prototype.constructor = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index fc9df4d..4f2d066 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -1,4 +1,5 @@ -var Rectangle = require('./Rectangle'); +var Rectangle = require('./Rectangle'), + CONST = require('../../const'); /** * The Ellipse object can be used to specify a hit area for displayObjects @@ -36,10 +37,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.ELIP in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.ELIP; } Ellipse.prototype.constructor = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index 5d4a464..ae89148 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -1,4 +1,5 @@ -var Point = require('../Point'); +var Point = require('../Point'), + CONST = require('../../const'); /** * @class @@ -33,6 +34,13 @@ * @member {Point[]} */ this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + */ + this.type = CONST.SHAPES.POLY; } Polygon.prototype.constructor = Polygon; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index c3454b5..16ac62a 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * the Rectangle object is an area defined by its position, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -34,10 +36,11 @@ this.height = height || 0; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RECT; } Rectangle.prototype.constructor = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0f2abfc..27d286e 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -1,3 +1,5 @@ +var CONST = require('../../const'); + /** * The Rounded Rectangle object is an area defined by its position and has nice rounded corners, as indicated by its top-left corner point (x, y) and by its width and its height. * @@ -41,10 +43,11 @@ this.radius = radius || 20; /** - * The type of the object, should be one of the Graphics type consts, Graphics.RRECT in this case + * The type of the object, mainly used to avoid `instanceof` checks * * @member {number} */ + this.type = CONST.SHAPES.RREC; } RoundedRectangle.prototype.constructor = RoundedRectangle; diff --git a/src/core/primitives/Graphics.js b/src/core/primitives/Graphics.js new file mode 100644 index 0000000..42647c5 --- /dev/null +++ b/src/core/primitives/Graphics.js @@ -0,0 +1,1029 @@ +var core = require('../core'), + GraphicsData = require('./GraphicsData'); + +/** + * 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() { + core.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 {object[]} + * @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 = core.CONST.blendModes.NORMAL; + + /** + * Current path + * + * @member {object} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object[]} + * @private + */ + 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 core.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(core.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; + } + } + } +}); + +/** + * 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 core.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 core.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; + + 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 core.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 core.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 core.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 core.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 core.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 core.CanvasBuffer(bounds.width * resolution, bounds.height * resolution); + + var texture = core.Texture.fromCanvas(canvasBuffer.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + canvasBuffer.context.scale(resolution, resolution); + + canvasBuffer.context.translate(-bounds.x,-bounds.y); + + core.CanvasGraphics.renderGraphics(this, canvasBuffer.context); + + return texture; +}; + +/** + * Renders the object using the WebGL renderer + * + * @method _renderWebGL + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderWebGL = function (renderSession) { + // 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 on the gpu too! + this.updateCachedSpriteTexture(); + + this.cachedSpriteDirty = false; + this.dirty = false; + } + + this._cachedSprite.worldAlpha = this.worldAlpha; + + core.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); + + return; + } + else { + renderSession.spriteBatch.stop(); + renderSession.blendModeManager.setBlendMode(this.blendMode); + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + if (this._filters) { + renderSession.filterManager.pushFilter(this._filterBlock); + } + + // check blend mode + if (this.blendMode !== renderSession.spriteBatch.currentBlendMode) { + renderSession.spriteBatch.currentBlendMode = this.blendMode; + + var blendModeWebGL = blendModesWebGL[renderSession.spriteBatch.currentBlendMode]; + + renderSession.spriteBatch.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); + } + + // check if the webgl graphic needs to be updated + if (this.glDirty) { + this.dirty = true; + this.glDirty = false; + } + + core.WebGLGraphics.renderGraphics(this, renderSession); + + // only render if it has children! + if (this.children.length) { + renderSession.spriteBatch.start(); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderWebGL(renderSession); + } + + renderSession.spriteBatch.stop(); + } + + if (this._filters) { + renderSession.filterManager.popFilter(); + } + + if (this._mask) { + renderSession.maskManager.popMask(this.mask, renderSession); + } + + renderSession.drawCount++; + + renderSession.spriteBatch.start(); + } +}; + +/** + * Renders the object using the Canvas renderer + * + * @method _renderCanvas + * @param renderSession {RenderSession} + * @private + */ +Graphics.prototype._renderCanvas = function (renderSession) { + // 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; + + core.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession); + + return; + } + else { + var context = renderSession.context; + var transform = this.worldTransform; + + if (this.blendMode !== renderSession.currentBlendMode) { + renderSession.currentBlendMode = this.blendMode; + context.globalCompositeOperation = blendModesCanvas[renderSession.currentBlendMode]; + } + + if (this._mask) { + renderSession.maskManager.pushMask(this._mask, renderSession); + } + + var resolution = renderSession.resolution; + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + core.CanvasGraphics.renderGraphics(this, context); + + for (var i = 0, j = this.children.length; i < j; ++i) { + this.children[i]._renderCanvas(renderSession); + } + + if (this._mask) { + renderSession.maskManager.popMask(renderSession); + } + } +}; + +/** + * 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 core.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 === core.CONST.SHAPES.RECT || type === core.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 === core.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 === core.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 core.CanvasBuffer(bounds.width, bounds.height); + var texture = core.Texture.fromCanvas(canvasBuffer.canvas); + + this._cachedSprite = new core.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.. + core.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 === core.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 new file mode 100644 index 0000000..9377ef7 --- /dev/null +++ b/src/core/primitives/GraphicsData.js @@ -0,0 +1,23 @@ +/** + * 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; diff --git a/src/core/primitives/index.js b/src/core/primitives/index.js new file mode 100644 index 0000000..7f02649 --- /dev/null +++ b/src/core/primitives/index.js @@ -0,0 +1,14 @@ +/** + * @file Main export of the PIXI primitives library + * @author Mat Groves + * @copyright 2013-2015 GoodBoyDigital + * @license {@link https://github.com/GoodBoyDigital/pixi.js/blob/master/LICENSE|MIT License} + */ + +/** + * @namespace PIXI + */ +module.exports = { + Graphics: require('./Graphics'), + GraphicsData: require('./GraphicsData') +}; diff --git a/src/core/renderers/canvas/CanvasGraphics.js b/src/core/renderers/canvas/CanvasGraphics.js index 2b1797a..ef7e765 100644 --- a/src/core/renderers/canvas/CanvasGraphics.js +++ b/src/core/renderers/canvas/CanvasGraphics.js @@ -1,4 +1,4 @@ -var Graphics = require('../../../primitives/Graphics'); +var CONST = require('../../const'); /** * A set of functions used by the canvas renderer to draw the primitive graphics data. @@ -30,7 +30,7 @@ context.lineWidth = data.lineWidth; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -61,7 +61,7 @@ context.stroke(); } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { if (data.fillColor || data.fillColor === 0) { context.globalAlpha = data.fillAlpha * worldAlpha; @@ -75,7 +75,7 @@ context.strokeRect(shape.x, shape.y, shape.width, shape.height); } } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); @@ -92,7 +92,7 @@ context.stroke(); } } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas var w = shape.width * 2; @@ -130,7 +130,7 @@ context.stroke(); } } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var rx = shape.x; var ry = shape.y; var width = shape.width; @@ -190,7 +190,7 @@ var data = graphics.graphicsData[i]; var shape = data.shape; - if (data.type === Graphics.POLY) { + if (data.type === CONST.SHAPES.POLY) { context.beginPath(); var points = shape.points; @@ -207,18 +207,18 @@ } } - else if (data.type === Graphics.RECT) { + else if (data.type === CONST.SHAPES.RECT) { context.beginPath(); context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } - else if (data.type === Graphics.CIRC) { + else if (data.type === CONST.SHAPES.CIRC) { // TODO - need to be Undefined! context.beginPath(); context.arc(shape.x, shape.y, shape.radius,0,2*Math.PI); context.closePath(); } - else if (data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.ELIP) { // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas @@ -245,7 +245,7 @@ context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); context.closePath(); } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { var pts = shape.points; var rx = pts[0]; diff --git a/src/core/renderers/webgl/managers/WebGLBlendModeManager.js b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js new file mode 100644 index 0000000..9f2f89a --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLBlendModeManager.js @@ -0,0 +1,37 @@ +var WebGLManager = require('./WebGLManager'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLBlendModeManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; +} + +WebGLBlendModeManager.prototype = Object.create(WebGLManager.prototype); +WebGLBlendModeManager.prototype.constructor = WebGLBlendModeManager; +module.exports = WebGLBlendModeManager; + +/** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as BlendModes.ADD + */ +WebGLBlendModeManager.prototype.setBlendMode = function (blendMode) { + if (this.currentBlendMode === blendMode) { + return false; + } + + this.currentBlendMode = blendMode; + + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; +}; diff --git a/src/core/renderers/webgl/managers/WebGLFilterManager.js b/src/core/renderers/webgl/managers/WebGLFilterManager.js new file mode 100644 index 0000000..176e7cd --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLFilterManager.js @@ -0,0 +1,446 @@ +var WebGLManager = require('./WebGLManager'), + FilterTexture = require('./FilterTexture'), + PixiShader = require('../shaders/PixiShader'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLFilterManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {any[]} + */ + this.filterStack = []; + + /** + * @member {any[]]} + */ + this.texturePool = []; + + /** + * @member {number} + */ + this.offsetX = 0; + + /** + * @member {number} + */ + this.offsetY = 0; + + // listen for context and update necessary buffers + var self = this; + this.renderer.on('context', function () { + self.texturePool.length = 0; + self.initShaderBuffers(); + }); +} + +WebGLFilterManager.prototype = Object.create(WebGLManager.prototype); +WebGLFilterManager.prototype.constructor = WebGLFilterManager; +module.exports = WebGLFilterManager; + +/** + * @param renderer {WebGLRenderer} + * @param buffer {ArrayBuffer} + */ +WebGLFilterManager.prototype.begin = function (renderer, buffer) { + this.renderer = renderer; + this.defaultShader = renderer.shaderManager.defaultShader; + + this.width = renderer.projection.x * 2; + this.height = -renderer.projection.y * 2; + + this.buffer = buffer; +}; + +/** + * Applies the filter and adds it to the current filter stack. + * + * @param filterBlock {object} the filter that will be pushed to the current filter stack + */ +WebGLFilterManager.prototype.pushFilter = function (filterBlock) { + var gl = this.renderer.gl; + + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds(); + + // filter program + // OPTIMISATION - the first filter is free if its a simple color change? + this.filterStack.push(filterBlock); + + var filter = filterBlock.filterPasses[0]; + + this.offsetX += filterBlock._filterArea.x; + this.offsetY += filterBlock._filterArea.y; + + var texture = this.texturePool.pop(); + if (!texture) { + texture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + else { + texture.resize(this.width, this.height); + } + + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea; + + var padding = filter.padding; + filterArea.x -= padding; + filterArea.y -= padding; + filterArea.width += padding * 2; + filterArea.height += padding * 2; + + // cap filter to screen size.. + if (filterArea.x < 0) { + filterArea.x = 0; + } + + if (filterArea.width > this.width) { + filterArea.width = this.width; + } + + if (filterArea.y < 0) { + filterArea.y = 0; + } + + if (filterArea.height > this.height) { + filterArea.height = this.height; + } + + //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); + + // set view port + gl.viewport(0, 0, filterArea.width, filterArea.height); + + projection.x = filterArea.width/2; + projection.y = -filterArea.height/2; + + offset.x = -filterArea.x; + offset.y = -filterArea.y; + + // update projection + // now restore the regular shader.. + // this.renderer.shaderManager.setShader(this.defaultShader); + //gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); + //gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y); + + gl.colorMask(true, true, true, true); + gl.clearColor(0,0,0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + + filterBlock._glFilterTexture = texture; + +}; + +/** + * Removes the last filter from the filter stack and doesn't return it. + * + */ +WebGLFilterManager.prototype.popFilter = function () { + var gl = this.renderer.gl; + + var filterBlock = this.filterStack.pop(); + var filterArea = filterBlock._filterArea; + var texture = filterBlock._glFilterTexture; + var projection = this.renderer.projection; + var offset = this.renderer.offset; + + if (filterBlock.filterPasses.length > 1) { + gl.viewport(0, 0, filterArea.width, filterArea.height); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = 0; + this.vertexArray[1] = filterArea.height; + + this.vertexArray[2] = filterArea.width; + this.vertexArray[3] = filterArea.height; + + this.vertexArray[4] = 0; + this.vertexArray[5] = 0; + + this.vertexArray[6] = filterArea.width; + this.vertexArray[7] = 0; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + // now set the uvs.. + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + var inputTexture = texture; + var outputTexture = this.texturePool.pop(); + if (!outputTexture) { + outputTexture = new FilterTexture(this.renderer.gl, this.width, this.height); + } + outputTexture.resize(this.width, this.height); + + // need to clear this FBO as it may have some left over elements from a previous filter. + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + gl.clear(gl.COLOR_BUFFER_BIT); + + gl.disable(gl.BLEND); + + for (var i = 0; i < filterBlock.filterPasses.length-1; i++) { + var filterPass = filterBlock.filterPasses[i]; + + gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); + + // draw texture.. + //filterPass.applyFilterPass(filterArea.width, filterArea.height); + this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height); + + // swap the textures.. + var temp = inputTexture; + inputTexture = outputTexture; + outputTexture = temp; + } + + gl.enable(gl.BLEND); + + texture = inputTexture; + this.texturePool.push(outputTexture); + } + + var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1]; + + this.offsetX -= filterArea.x; + this.offsetY -= filterArea.y; + + var sizeX = this.width; + var sizeY = this.height; + + var offsetX = 0; + var offsetY = 0; + + var buffer = this.buffer; + + // time to render the filters texture to the previous scene + if (this.filterStack.length === 0) { + gl.colorMask(true, true, true, true);//this.transparent); + } + else { + var currentFilter = this.filterStack[this.filterStack.length-1]; + filterArea = currentFilter._filterArea; + + sizeX = filterArea.width; + sizeY = filterArea.height; + + offsetX = filterArea.x; + offsetY = filterArea.y; + + buffer = currentFilter._glFilterTexture.frameBuffer; + } + + // TODO need to remove these global elements.. + projection.x = sizeX/2; + projection.y = -sizeY/2; + + offset.x = offsetX; + offset.y = offsetY; + + filterArea = filterBlock._filterArea; + + var x = filterArea.x-offsetX; + var y = filterArea.y-offsetY; + + // update the buffers.. + // make sure to flip the y! + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + this.vertexArray[0] = x; + this.vertexArray[1] = y + filterArea.height; + + this.vertexArray[2] = x + filterArea.width; + this.vertexArray[3] = y + filterArea.height; + + this.vertexArray[4] = x; + this.vertexArray[5] = y; + + this.vertexArray[6] = x + filterArea.width; + this.vertexArray[7] = y; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + + this.uvArray[2] = filterArea.width/this.width; + this.uvArray[5] = filterArea.height/this.height; + this.uvArray[6] = filterArea.width/this.width; + this.uvArray[7] = filterArea.height/this.height; + + gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); + + gl.viewport(0, 0, sizeX, sizeY); + + // bind the buffer + gl.bindFramebuffer(gl.FRAMEBUFFER, buffer ); + + // set the blend mode! + //gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) + + // set texture + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, texture.texture); + + // apply! + this.applyFilterPass(filter, filterArea, sizeX, sizeY); + + // now restore the regular shader.. should happen automatically now.. + // this.renderer.shaderManager.setShader(this.defaultShader); + // gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2); + // gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY); + + // return the texture to the pool + this.texturePool.push(texture); + filterBlock._glFilterTexture = null; +}; + + +/** + * Applies the filter to the specified area. + * + * @param filter {AbstractFilter} the filter that needs to be applied + * @param filterArea {Texture} TODO - might need an update + * @param width {number} the horizontal range of the filter + * @param height {number} the vertical range of the filter + */ +WebGLFilterManager.prototype.applyFilterPass = function (filter, filterArea, width, height) { + // use program + var gl = this.renderer.gl; + + var shader = filter.shaders[gl.id]; + + if (!shader) { + shader = new PixiShader(gl); + + shader.fragmentSrc = filter.fragmentSrc; + shader.uniforms = filter.uniforms; + shader.init(); + + filter.shaders[gl.id] = shader; + } + + // set the shader + this.renderer.shaderManager.setShader(shader); + +// gl.useProgram(shader.program); + + gl.uniform2f(shader.projectionVector, width/2, -height/2); + gl.uniform2f(shader.offsetVector, 0,0); + + if (filter.uniforms.dimensions) { + filter.uniforms.dimensions.value[0] = this.width;//width; + filter.uniforms.dimensions.value[1] = this.height;//height; + filter.uniforms.dimensions.value[2] = this.vertexArray[0]; + filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; + } + + shader.syncUniforms(); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, 0, 0); + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + + // draw the filter... + gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); + + this.renderer.drawCount++; +}; + +/** + * Initialises the shader buffers. + * + */ +WebGLFilterManager.prototype.initShaderBuffers = function () { + var gl = this.renderer.gl; + + // create some buffers + this.vertexBuffer = gl.createBuffer(); + this.uvBuffer = gl.createBuffer(); + this.colorBuffer = gl.createBuffer(); + this.indexBuffer = gl.createBuffer(); + + // bind and upload the vertexs.. + // keep a reference to the vertexFloatData.. + this.vertexArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW); + + // bind and upload the uv buffer + this.uvArray = new Float32Array([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.uvArray, gl.STATIC_DRAW); + + this.colorArray = new Float32Array([1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF, + 1.0, 0xFFFFFF]); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this.colorArray, gl.STATIC_DRAW); + + // bind and upload the index + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW); + +}; + +/** + * Destroys the filter and removes it from the filter stack. + * + */ +WebGLFilterManager.prototype.destroy = function () { + var gl = this.renderer.gl; + + this.filterStack = null; + + this.offsetX = 0; + this.offsetY = 0; + + // destroy textures + for (var i = 0; i < this.texturePool.length; i++) { + this.texturePool[i].destroy(); + } + + this.texturePool = null; + + //destroy buffers.. + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.uvBuffer); + gl.deleteBuffer(this.colorBuffer); + gl.deleteBuffer(this.indexBuffer); + + this.renderer = null; +}; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js new file mode 100644 index 0000000..9d47b5c --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -0,0 +1,20 @@ +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLManager(renderer) { + /** + * The renderer this manager works for. + * + * @member {WebGLRenderer} + */ + this.renderer = renderer; +} + +WebGLManager.prototype.constructor = WebGLManager; +module.exports = WebGLManager; + +WebGLManager.prototype.destroy = function () { + this.renderer = null; +}; diff --git a/src/core/renderers/webgl/managers/WebGLMaskManager.js b/src/core/renderers/webgl/managers/WebGLMaskManager.js new file mode 100644 index 0000000..6d46d85 --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLMaskManager.js @@ -0,0 +1,41 @@ +var WebGLManager = require('./WebGLManager'), + WebGLGraphics = require('./WebGLGraphics'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLMaskManager(renderer) { + WebGLManager.call(this, renderer); +} + +WebGLMaskManager.prototype = Object.create(WebGLManager.prototype); +WebGLMaskManager.prototype.constructor = WebGLMaskManager; +module.exports = WebGLMaskManager; + +/** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {any[]} + */ +WebGLMaskManager.prototype.pushMask = function (maskData) { + if (maskData.dirty) { + WebGLGraphics.updateGraphics(maskData, this.renderer.gl); + } + + if (!maskData._webGL[this.renderer.gl.id].data.length) { + return; + } + + this.renderer.stencilManager.pushStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); +}; + +/** + * Removes the last filter from the filter stack and doesn't return it. + * + * @param maskData {any[]} + */ +WebGLMaskManager.prototype.popMask = function (maskData) { + this.renderer.stencilManager.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 new file mode 100644 index 0000000..d02d95d --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLShaderManager.js @@ -0,0 +1,172 @@ +var WebGLManager = require('./WebGLManager'), + PrimitiveShader = require('../shaders/PrimitiveShader'), + ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), + PixiShader = require('../shaders/PixiShader'), + PixiFastShader = require('../shaders/PixiFastShader'), + StripShader = require('../shaders/StripShader'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLShaderManager(renderer) { + WebGLManager.call(this, renderer); + + /** + * @member {number} + */ + this.maxAttibs = 10; + + /** + * @member {any[]} + */ + this.attribState = []; + + /** + * @member {any[]} + */ + this.tempAttribState = []; + + for (var i = 0; i < this.maxAttibs; i++) { + this.attribState[i] = false; + } + + /** + * @member {any[]} + */ + this.stack = []; + + /** + * @member {number} + * @private + */ + this._currentId = -1; + + /** + * @member {Shader} + * @private + */ + 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; + + // listen for context and update necessary shaders + var self = this; + this.renderer.on('context', function (gl) { + // 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 PixiShader(gl); + + // this shader is used for the fast sprite rendering + self.fastShader = new PixiFastShader(gl); + + // the next one is used for rendering triangle strips + self.stripShader = new StripShader(gl); + + self.setShader(self.defaultShader); + }); +} + +WebGLShaderManager.prototype = Object.create(WebGLManager.prototype); +WebGLShaderManager.prototype.constructor = WebGLShaderManager; +module.exports = WebGLShaderManager; + +/** + * Takes the attributes given in parameters. + * + * @param attribs {Array} attribs + */ +WebGLShaderManager.prototype.setAttribs = function (attribs) { + // reset temp state + var i; + + for (i = 0; i < this.tempAttribState.length; i++) { + this.tempAttribState[i] = false; + } + + // set the new attribs + for (var a in attribs) { + this.tempAttribState[attribs[a]] = true; + } + + var gl = this.renderer.gl; + + for (i = 0; i < this.attribState.length; i++) { + if (this.attribState[i] !== this.tempAttribState[i]) { + this.attribState[i] = this.tempAttribState[i]; + + if (this.attribState[i]) { + gl.enableVertexAttribArray(i); + } + else { + gl.disableVertexAttribArray(i); + } + } + } +}; + +/** + * Sets the current shader. + * + * @param shader {Any} + */ +WebGLShaderManager.prototype.setShader = function (shader) { + if (this._currentId === shader.uuid) { + return false; + } + + this._currentId = shader.uuid; + + this.currentShader = shader; + + this.renderer.gl.useProgram(shader.program); + this.setAttribs(shader.attributes); + + return true; +}; + +/** + * Destroys this object. + * + */ +WebGLShaderManager.prototype.destroy = function () { + 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/managers/WebGLStencilManager.js b/src/core/renderers/webgl/managers/WebGLStencilManager.js new file mode 100644 index 0000000..0bedae6 --- /dev/null +++ b/src/core/renderers/webgl/managers/WebGLStencilManager.js @@ -0,0 +1,256 @@ +var WebGLManager = require('./WebGLManager'), + utils = require('../../../utils'); + +/** + * @class + * @namespace PIXI + * @param renderer {WebGLRenderer} The renderer this manager works for. + */ +function WebGLStencilManager(renderer) { + WebGLManager.call(this, renderer); + + this.stencilStack = []; + this.reverse = true; + this.count = 0; +} + +WebGLStencilManager.prototype = Object.create(WebGLManager.prototype); +WebGLStencilManager.prototype.constructor = WebGLStencilManager; +module.exports = WebGLStencilManager; + +/** + * Applies the Mask and adds it to the current filter stack. + * + * @param graphics {Graphics} + * @param webGLData {any[]} + */ +WebGLStencilManager.prototype.pushStencil = function (graphics, webGLData) { + var gl = this.renderer.gl; + + this.bindGraphics(graphics, webGLData, this.renderer); + + if (this.stencilStack.length === 0) { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + this.reverse = true; + this.count = 0; + } + + this.stencilStack.push(webGLData); + + var level = this.count; + + gl.colorMask(false, false, false, false); + + gl.stencilFunc(gl.ALWAYS,0,0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); + + // draw the triangle strip! + + if (webGLData.mode === 1) { + gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + + // draw a quad to increment.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + } + + this.reverse = !this.reverse; + } + else { + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + } + } + + gl.colorMask(true, true, true, true); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + + this.count++; +}; + +/** + * TODO this does not belong here! + * + * @param graphics {Graphics} + * @param webGLData {Array} + */ +WebGLStencilManager.prototype.bindGraphics = function (graphics, webGLData) { + //if (this._currentGraphics === graphics)return; + this._currentGraphics = graphics; + + var gl = this.renderer.gl; + + // bind the graphics object.. + var projection = this.renderer.projection, + offset = this.renderer.offset, + shader;// = this.renderer.shaderManager.primitiveShader; + + if (webGLData.mode === 1) { + shader = this.renderer.shaderManager.complexPrimitiveShader; + + this.renderer.shaderManager.setShader(shader); + + gl.uniform1f(shader.flipY, this.renderer.flipY); + + gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); + + gl.uniform2f(shader.projectionVector, projection.x, -projection.y); + gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); + + gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); + gl.uniform3fv(shader.color, webGLData.color); + + gl.uniform1f(shader.alpha, graphics.worldAlpha * webGLData.alpha); + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 2, 0); + + + // now do the rest.. + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + } + else { + //this.renderer.shaderManager.activatePrimitiveShader(); + shader = this.renderer.shaderManager.primitiveShader; + this.renderer.shaderManager.setShader( shader ); + + gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); + + gl.uniform1f(shader.flipY, this.renderer.flipY); + gl.uniform2f(shader.projectionVector, projection.x, -projection.y); + gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); + + gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); + + gl.uniform1f(shader.alpha, graphics.worldAlpha); + + gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); + + gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); + gl.vertexAttribPointer(shader.colorAttribute, 4, gl.FLOAT, false,4 * 6, 2 * 4); + + // set the index buffer! + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); + } +}; + +/** + * @param graphics {Graphics} + * @param webGLData {Array} + */ +WebGLStencilManager.prototype.popStencil = function (graphics, webGLData) { + var gl = this.renderer.gl; + + this.stencilStack.pop(); + + this.count--; + + if (this.stencilStack.length === 0) { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + + } + else { + + var level = this.count; + + this.bindGraphics(graphics, webGLData, this.renderer); + + gl.colorMask(false, false, false, false); + + if (webGLData.mode === 1) { + this.reverse = !this.reverse; + + if (this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + + // draw a quad to increment.. + gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); + + gl.stencilFunc(gl.ALWAYS,0,0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); + + // draw the triangle strip! + gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + } + + } + else { + // console.log("<<>>") + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); + } + else { + gl.stencilFunc(gl.EQUAL,level+1, 0xFF); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + } + + gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); + + if (!this.reverse) { + gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); + } + else { + gl.stencilFunc(gl.EQUAL,level, 0xFF); + } + } + + gl.colorMask(true, true, true, true); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + + + } +}; + +/** + * Destroys the mask stack. + * + */ +WebGLStencilManager.prototype.destroy = function () { + this.renderer = null; + this.stencilStack = null; +}; diff --git a/src/core/renderers/webgl/utils/WebGLBlendModeManager.js b/src/core/renderers/webgl/utils/WebGLBlendModeManager.js deleted file mode 100644 index 9f2f89a..0000000 --- a/src/core/renderers/webgl/utils/WebGLBlendModeManager.js +++ /dev/null @@ -1,37 +0,0 @@ -var WebGLManager = require('./WebGLManager'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLBlendModeManager(renderer) { - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -WebGLBlendModeManager.prototype = Object.create(WebGLManager.prototype); -WebGLBlendModeManager.prototype.constructor = WebGLBlendModeManager; -module.exports = WebGLBlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as BlendModes.ADD - */ -WebGLBlendModeManager.prototype.setBlendMode = function (blendMode) { - if (this.currentBlendMode === blendMode) { - return false; - } - - this.currentBlendMode = blendMode; - - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); - - return true; -}; diff --git a/src/core/renderers/webgl/utils/WebGLFilterManager.js b/src/core/renderers/webgl/utils/WebGLFilterManager.js deleted file mode 100644 index 176e7cd..0000000 --- a/src/core/renderers/webgl/utils/WebGLFilterManager.js +++ /dev/null @@ -1,446 +0,0 @@ -var WebGLManager = require('./WebGLManager'), - FilterTexture = require('./FilterTexture'), - PixiShader = require('../shaders/PixiShader'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLFilterManager(renderer) { - WebGLManager.call(this, renderer); - - /** - * @member {any[]} - */ - this.filterStack = []; - - /** - * @member {any[]]} - */ - this.texturePool = []; - - /** - * @member {number} - */ - this.offsetX = 0; - - /** - * @member {number} - */ - this.offsetY = 0; - - // listen for context and update necessary buffers - var self = this; - this.renderer.on('context', function () { - self.texturePool.length = 0; - self.initShaderBuffers(); - }); -} - -WebGLFilterManager.prototype = Object.create(WebGLManager.prototype); -WebGLFilterManager.prototype.constructor = WebGLFilterManager; -module.exports = WebGLFilterManager; - -/** - * @param renderer {WebGLRenderer} - * @param buffer {ArrayBuffer} - */ -WebGLFilterManager.prototype.begin = function (renderer, buffer) { - this.renderer = renderer; - this.defaultShader = renderer.shaderManager.defaultShader; - - this.width = renderer.projection.x * 2; - this.height = -renderer.projection.y * 2; - - this.buffer = buffer; -}; - -/** - * Applies the filter and adds it to the current filter stack. - * - * @param filterBlock {object} the filter that will be pushed to the current filter stack - */ -WebGLFilterManager.prototype.pushFilter = function (filterBlock) { - var gl = this.renderer.gl; - - var projection = this.renderer.projection; - var offset = this.renderer.offset; - - filterBlock._filterArea = filterBlock.target.filterArea || filterBlock.target.getBounds(); - - // filter program - // OPTIMISATION - the first filter is free if its a simple color change? - this.filterStack.push(filterBlock); - - var filter = filterBlock.filterPasses[0]; - - this.offsetX += filterBlock._filterArea.x; - this.offsetY += filterBlock._filterArea.y; - - var texture = this.texturePool.pop(); - if (!texture) { - texture = new FilterTexture(this.renderer.gl, this.width, this.height); - } - else { - texture.resize(this.width, this.height); - } - - gl.bindTexture(gl.TEXTURE_2D, texture.texture); - - var filterArea = filterBlock._filterArea;// filterBlock.target.getBounds();///filterBlock.target.filterArea; - - var padding = filter.padding; - filterArea.x -= padding; - filterArea.y -= padding; - filterArea.width += padding * 2; - filterArea.height += padding * 2; - - // cap filter to screen size.. - if (filterArea.x < 0) { - filterArea.x = 0; - } - - if (filterArea.width > this.width) { - filterArea.width = this.width; - } - - if (filterArea.y < 0) { - filterArea.y = 0; - } - - if (filterArea.height > this.height) { - filterArea.height = this.height; - } - - //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, filterArea.width, filterArea.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - gl.bindFramebuffer(gl.FRAMEBUFFER, texture.frameBuffer); - - // set view port - gl.viewport(0, 0, filterArea.width, filterArea.height); - - projection.x = filterArea.width/2; - projection.y = -filterArea.height/2; - - offset.x = -filterArea.x; - offset.y = -filterArea.y; - - // update projection - // now restore the regular shader.. - // this.renderer.shaderManager.setShader(this.defaultShader); - //gl.uniform2f(this.defaultShader.projectionVector, filterArea.width/2, -filterArea.height/2); - //gl.uniform2f(this.defaultShader.offsetVector, -filterArea.x, -filterArea.y); - - gl.colorMask(true, true, true, true); - gl.clearColor(0,0,0, 0); - gl.clear(gl.COLOR_BUFFER_BIT); - - filterBlock._glFilterTexture = texture; - -}; - -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -WebGLFilterManager.prototype.popFilter = function () { - var gl = this.renderer.gl; - - var filterBlock = this.filterStack.pop(); - var filterArea = filterBlock._filterArea; - var texture = filterBlock._glFilterTexture; - var projection = this.renderer.projection; - var offset = this.renderer.offset; - - if (filterBlock.filterPasses.length > 1) { - gl.viewport(0, 0, filterArea.width, filterArea.height); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - - this.vertexArray[0] = 0; - this.vertexArray[1] = filterArea.height; - - this.vertexArray[2] = filterArea.width; - this.vertexArray[3] = filterArea.height; - - this.vertexArray[4] = 0; - this.vertexArray[5] = 0; - - this.vertexArray[6] = filterArea.width; - this.vertexArray[7] = 0; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - // now set the uvs.. - this.uvArray[2] = filterArea.width/this.width; - this.uvArray[5] = filterArea.height/this.height; - this.uvArray[6] = filterArea.width/this.width; - this.uvArray[7] = filterArea.height/this.height; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); - - var inputTexture = texture; - var outputTexture = this.texturePool.pop(); - if (!outputTexture) { - outputTexture = new FilterTexture(this.renderer.gl, this.width, this.height); - } - outputTexture.resize(this.width, this.height); - - // need to clear this FBO as it may have some left over elements from a previous filter. - gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); - gl.clear(gl.COLOR_BUFFER_BIT); - - gl.disable(gl.BLEND); - - for (var i = 0; i < filterBlock.filterPasses.length-1; i++) { - var filterPass = filterBlock.filterPasses[i]; - - gl.bindFramebuffer(gl.FRAMEBUFFER, outputTexture.frameBuffer ); - - // set texture - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, inputTexture.texture); - - // draw texture.. - //filterPass.applyFilterPass(filterArea.width, filterArea.height); - this.applyFilterPass(filterPass, filterArea, filterArea.width, filterArea.height); - - // swap the textures.. - var temp = inputTexture; - inputTexture = outputTexture; - outputTexture = temp; - } - - gl.enable(gl.BLEND); - - texture = inputTexture; - this.texturePool.push(outputTexture); - } - - var filter = filterBlock.filterPasses[filterBlock.filterPasses.length-1]; - - this.offsetX -= filterArea.x; - this.offsetY -= filterArea.y; - - var sizeX = this.width; - var sizeY = this.height; - - var offsetX = 0; - var offsetY = 0; - - var buffer = this.buffer; - - // time to render the filters texture to the previous scene - if (this.filterStack.length === 0) { - gl.colorMask(true, true, true, true);//this.transparent); - } - else { - var currentFilter = this.filterStack[this.filterStack.length-1]; - filterArea = currentFilter._filterArea; - - sizeX = filterArea.width; - sizeY = filterArea.height; - - offsetX = filterArea.x; - offsetY = filterArea.y; - - buffer = currentFilter._glFilterTexture.frameBuffer; - } - - // TODO need to remove these global elements.. - projection.x = sizeX/2; - projection.y = -sizeY/2; - - offset.x = offsetX; - offset.y = offsetY; - - filterArea = filterBlock._filterArea; - - var x = filterArea.x-offsetX; - var y = filterArea.y-offsetY; - - // update the buffers.. - // make sure to flip the y! - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - - this.vertexArray[0] = x; - this.vertexArray[1] = y + filterArea.height; - - this.vertexArray[2] = x + filterArea.width; - this.vertexArray[3] = y + filterArea.height; - - this.vertexArray[4] = x; - this.vertexArray[5] = y; - - this.vertexArray[6] = x + filterArea.width; - this.vertexArray[7] = y; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexArray); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - - this.uvArray[2] = filterArea.width/this.width; - this.uvArray[5] = filterArea.height/this.height; - this.uvArray[6] = filterArea.width/this.width; - this.uvArray[7] = filterArea.height/this.height; - - gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.uvArray); - - gl.viewport(0, 0, sizeX, sizeY); - - // bind the buffer - gl.bindFramebuffer(gl.FRAMEBUFFER, buffer ); - - // set the blend mode! - //gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) - - // set texture - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, texture.texture); - - // apply! - this.applyFilterPass(filter, filterArea, sizeX, sizeY); - - // now restore the regular shader.. should happen automatically now.. - // this.renderer.shaderManager.setShader(this.defaultShader); - // gl.uniform2f(this.defaultShader.projectionVector, sizeX/2, -sizeY/2); - // gl.uniform2f(this.defaultShader.offsetVector, -offsetX, -offsetY); - - // return the texture to the pool - this.texturePool.push(texture); - filterBlock._glFilterTexture = null; -}; - - -/** - * Applies the filter to the specified area. - * - * @param filter {AbstractFilter} the filter that needs to be applied - * @param filterArea {Texture} TODO - might need an update - * @param width {number} the horizontal range of the filter - * @param height {number} the vertical range of the filter - */ -WebGLFilterManager.prototype.applyFilterPass = function (filter, filterArea, width, height) { - // use program - var gl = this.renderer.gl; - - var shader = filter.shaders[gl.id]; - - if (!shader) { - shader = new PixiShader(gl); - - shader.fragmentSrc = filter.fragmentSrc; - shader.uniforms = filter.uniforms; - shader.init(); - - filter.shaders[gl.id] = shader; - } - - // set the shader - this.renderer.shaderManager.setShader(shader); - -// gl.useProgram(shader.program); - - gl.uniform2f(shader.projectionVector, width/2, -height/2); - gl.uniform2f(shader.offsetVector, 0,0); - - if (filter.uniforms.dimensions) { - filter.uniforms.dimensions.value[0] = this.width;//width; - filter.uniforms.dimensions.value[1] = this.height;//height; - filter.uniforms.dimensions.value[2] = this.vertexArray[0]; - filter.uniforms.dimensions.value[3] = this.vertexArray[5];//filterArea.height; - } - - shader.syncUniforms(); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); - gl.vertexAttribPointer(shader.colorAttribute, 2, gl.FLOAT, false, 0, 0); - - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - - // draw the filter... - gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); - - this.renderer.drawCount++; -}; - -/** - * Initialises the shader buffers. - * - */ -WebGLFilterManager.prototype.initShaderBuffers = function () { - var gl = this.renderer.gl; - - // create some buffers - this.vertexBuffer = gl.createBuffer(); - this.uvBuffer = gl.createBuffer(); - this.colorBuffer = gl.createBuffer(); - this.indexBuffer = gl.createBuffer(); - - // bind and upload the vertexs.. - // keep a reference to the vertexFloatData.. - this.vertexArray = new Float32Array([0.0, 0.0, - 1.0, 0.0, - 0.0, 1.0, - 1.0, 1.0]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.vertexArray, gl.STATIC_DRAW); - - // bind and upload the uv buffer - this.uvArray = new Float32Array([0.0, 0.0, - 1.0, 0.0, - 0.0, 1.0, - 1.0, 1.0]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.uvArray, gl.STATIC_DRAW); - - this.colorArray = new Float32Array([1.0, 0xFFFFFF, - 1.0, 0xFFFFFF, - 1.0, 0xFFFFFF, - 1.0, 0xFFFFFF]); - - gl.bindBuffer(gl.ARRAY_BUFFER, this.colorBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this.colorArray, gl.STATIC_DRAW); - - // bind and upload the index - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 1, 3, 2]), gl.STATIC_DRAW); - -}; - -/** - * Destroys the filter and removes it from the filter stack. - * - */ -WebGLFilterManager.prototype.destroy = function () { - var gl = this.renderer.gl; - - this.filterStack = null; - - this.offsetX = 0; - this.offsetY = 0; - - // destroy textures - for (var i = 0; i < this.texturePool.length; i++) { - this.texturePool[i].destroy(); - } - - this.texturePool = null; - - //destroy buffers.. - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.uvBuffer); - gl.deleteBuffer(this.colorBuffer); - gl.deleteBuffer(this.indexBuffer); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/WebGLGraphics.js b/src/core/renderers/webgl/utils/WebGLGraphics.js index 09f540f..1d1fe40 100644 --- a/src/core/renderers/webgl/utils/WebGLGraphics.js +++ b/src/core/renderers/webgl/utils/WebGLGraphics.js @@ -1,6 +1,5 @@ var utils = require('../../../utils'), math = require('../../../math'), - Graphics = require('../../../../primitives/Graphics'), WebGLGraphicsData = require('./WebGLGraphicsData'); /** @@ -122,7 +121,7 @@ for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) { var data = graphics.graphicsData[i]; - if (data.type === Graphics.POLY) { + 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) { @@ -164,13 +163,13 @@ else { webGLData = WebGLGraphics.switchMode(webGL, 0); - if (data.type === Graphics.RECT) { + if (data.type === CONST.SHAPES.RECT) { WebGLGraphics.buildRectangle(data, webGLData); } - else if (data.type === Graphics.CIRC || data.type === Graphics.ELIP) { + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) { WebGLGraphics.buildCircle(data, webGLData); } - else if (data.type === Graphics.RREC) { + else if (data.type === CONST.SHAPES.RREC) { WebGLGraphics.buildRoundedRectangle(data, webGLData); } } @@ -415,7 +414,7 @@ var height; // TODO - bit hacky?? - if (graphicsData.type === Graphics.CIRC) { + if (graphicsData.type === CONST.SHAPES.CIRC) { width = circleData.radius; height = circleData.radius; } diff --git a/src/core/renderers/webgl/utils/WebGLManager.js b/src/core/renderers/webgl/utils/WebGLManager.js deleted file mode 100644 index 9d47b5c..0000000 --- a/src/core/renderers/webgl/utils/WebGLManager.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLManager(renderer) { - /** - * The renderer this manager works for. - * - * @member {WebGLRenderer} - */ - this.renderer = renderer; -} - -WebGLManager.prototype.constructor = WebGLManager; -module.exports = WebGLManager; - -WebGLManager.prototype.destroy = function () { - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/WebGLMaskManager.js b/src/core/renderers/webgl/utils/WebGLMaskManager.js deleted file mode 100644 index 6d46d85..0000000 --- a/src/core/renderers/webgl/utils/WebGLMaskManager.js +++ /dev/null @@ -1,41 +0,0 @@ -var WebGLManager = require('./WebGLManager'), - WebGLGraphics = require('./WebGLGraphics'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLMaskManager(renderer) { - WebGLManager.call(this, renderer); -} - -WebGLMaskManager.prototype = Object.create(WebGLManager.prototype); -WebGLMaskManager.prototype.constructor = WebGLMaskManager; -module.exports = WebGLMaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {any[]} - */ -WebGLMaskManager.prototype.pushMask = function (maskData) { - if (maskData.dirty) { - WebGLGraphics.updateGraphics(maskData, this.renderer.gl); - } - - if (!maskData._webGL[this.renderer.gl.id].data.length) { - return; - } - - this.renderer.stencilManager.pushStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); -}; - -/** - * Removes the last filter from the filter stack and doesn't return it. - * - * @param maskData {any[]} - */ -WebGLMaskManager.prototype.popMask = function (maskData) { - this.renderer.stencilManager.popStencil(maskData, maskData._webGL[this.renderer.gl.id].data[0], this.renderer); -}; diff --git a/src/core/renderers/webgl/utils/WebGLShaderManager.js b/src/core/renderers/webgl/utils/WebGLShaderManager.js deleted file mode 100644 index d02d95d..0000000 --- a/src/core/renderers/webgl/utils/WebGLShaderManager.js +++ /dev/null @@ -1,172 +0,0 @@ -var WebGLManager = require('./WebGLManager'), - PrimitiveShader = require('../shaders/PrimitiveShader'), - ComplexPrimitiveShader = require('../shaders/ComplexPrimitiveShader'), - PixiShader = require('../shaders/PixiShader'), - PixiFastShader = require('../shaders/PixiFastShader'), - StripShader = require('../shaders/StripShader'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLShaderManager(renderer) { - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.maxAttibs = 10; - - /** - * @member {any[]} - */ - this.attribState = []; - - /** - * @member {any[]} - */ - this.tempAttribState = []; - - for (var i = 0; i < this.maxAttibs; i++) { - this.attribState[i] = false; - } - - /** - * @member {any[]} - */ - this.stack = []; - - /** - * @member {number} - * @private - */ - this._currentId = -1; - - /** - * @member {Shader} - * @private - */ - 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; - - // listen for context and update necessary shaders - var self = this; - this.renderer.on('context', function (gl) { - // 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 PixiShader(gl); - - // this shader is used for the fast sprite rendering - self.fastShader = new PixiFastShader(gl); - - // the next one is used for rendering triangle strips - self.stripShader = new StripShader(gl); - - self.setShader(self.defaultShader); - }); -} - -WebGLShaderManager.prototype = Object.create(WebGLManager.prototype); -WebGLShaderManager.prototype.constructor = WebGLShaderManager; -module.exports = WebGLShaderManager; - -/** - * Takes the attributes given in parameters. - * - * @param attribs {Array} attribs - */ -WebGLShaderManager.prototype.setAttribs = function (attribs) { - // reset temp state - var i; - - for (i = 0; i < this.tempAttribState.length; i++) { - this.tempAttribState[i] = false; - } - - // set the new attribs - for (var a in attribs) { - this.tempAttribState[attribs[a]] = true; - } - - var gl = this.renderer.gl; - - for (i = 0; i < this.attribState.length; i++) { - if (this.attribState[i] !== this.tempAttribState[i]) { - this.attribState[i] = this.tempAttribState[i]; - - if (this.attribState[i]) { - gl.enableVertexAttribArray(i); - } - else { - gl.disableVertexAttribArray(i); - } - } - } -}; - -/** - * Sets the current shader. - * - * @param shader {Any} - */ -WebGLShaderManager.prototype.setShader = function (shader) { - if (this._currentId === shader.uuid) { - return false; - } - - this._currentId = shader.uuid; - - this.currentShader = shader; - - this.renderer.gl.useProgram(shader.program); - this.setAttribs(shader.attributes); - - return true; -}; - -/** - * Destroys this object. - * - */ -WebGLShaderManager.prototype.destroy = function () { - 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/utils/WebGLStencilManager.js b/src/core/renderers/webgl/utils/WebGLStencilManager.js deleted file mode 100644 index 0bedae6..0000000 --- a/src/core/renderers/webgl/utils/WebGLStencilManager.js +++ /dev/null @@ -1,256 +0,0 @@ -var WebGLManager = require('./WebGLManager'), - utils = require('../../../utils'); - -/** - * @class - * @namespace PIXI - * @param renderer {WebGLRenderer} The renderer this manager works for. - */ -function WebGLStencilManager(renderer) { - WebGLManager.call(this, renderer); - - this.stencilStack = []; - this.reverse = true; - this.count = 0; -} - -WebGLStencilManager.prototype = Object.create(WebGLManager.prototype); -WebGLStencilManager.prototype.constructor = WebGLStencilManager; -module.exports = WebGLStencilManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param graphics {Graphics} - * @param webGLData {any[]} - */ -WebGLStencilManager.prototype.pushStencil = function (graphics, webGLData) { - var gl = this.renderer.gl; - - this.bindGraphics(graphics, webGLData, this.renderer); - - if (this.stencilStack.length === 0) { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - this.reverse = true; - this.count = 0; - } - - this.stencilStack.push(webGLData); - - var level = this.count; - - gl.colorMask(false, false, false, false); - - gl.stencilFunc(gl.ALWAYS,0,0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); - - // draw the triangle strip! - - if (webGLData.mode === 1) { - gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); - - if (this.reverse) { - gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); - } - else { - gl.stencilFunc(gl.EQUAL,level, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - } - - // draw a quad to increment.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - if (this.reverse) { - gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); - } - else { - gl.stencilFunc(gl.EQUAL,level+1, 0xFF); - } - - this.reverse = !this.reverse; - } - else { - if (!this.reverse) { - gl.stencilFunc(gl.EQUAL, 0xFF - level, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); - } - else { - gl.stencilFunc(gl.EQUAL,level, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - } - - gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); - - if (!this.reverse) { - gl.stencilFunc(gl.EQUAL,0xFF-(level+1), 0xFF); - } - else { - gl.stencilFunc(gl.EQUAL,level+1, 0xFF); - } - } - - gl.colorMask(true, true, true, true); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); - - this.count++; -}; - -/** - * TODO this does not belong here! - * - * @param graphics {Graphics} - * @param webGLData {Array} - */ -WebGLStencilManager.prototype.bindGraphics = function (graphics, webGLData) { - //if (this._currentGraphics === graphics)return; - this._currentGraphics = graphics; - - var gl = this.renderer.gl; - - // bind the graphics object.. - var projection = this.renderer.projection, - offset = this.renderer.offset, - shader;// = this.renderer.shaderManager.primitiveShader; - - if (webGLData.mode === 1) { - shader = this.renderer.shaderManager.complexPrimitiveShader; - - this.renderer.shaderManager.setShader(shader); - - gl.uniform1f(shader.flipY, this.renderer.flipY); - - gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); - - gl.uniform2f(shader.projectionVector, projection.x, -projection.y); - gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); - - gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); - gl.uniform3fv(shader.color, webGLData.color); - - gl.uniform1f(shader.alpha, graphics.worldAlpha * webGLData.alpha); - - gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); - - gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 2, 0); - - - // now do the rest.. - // set the index buffer! - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); - } - else { - //this.renderer.shaderManager.activatePrimitiveShader(); - shader = this.renderer.shaderManager.primitiveShader; - this.renderer.shaderManager.setShader( shader ); - - gl.uniformMatrix3fv(shader.translationMatrix, false, graphics.worldTransform.toArray(true)); - - gl.uniform1f(shader.flipY, this.renderer.flipY); - gl.uniform2f(shader.projectionVector, projection.x, -projection.y); - gl.uniform2f(shader.offsetVector, -offset.x, -offset.y); - - gl.uniform3fv(shader.tintColor, utils.hex2rgb(graphics.tint)); - - gl.uniform1f(shader.alpha, graphics.worldAlpha); - - gl.bindBuffer(gl.ARRAY_BUFFER, webGLData.buffer); - - gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 4 * 6, 0); - gl.vertexAttribPointer(shader.colorAttribute, 4, gl.FLOAT, false,4 * 6, 2 * 4); - - // set the index buffer! - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webGLData.indexBuffer); - } -}; - -/** - * @param graphics {Graphics} - * @param webGLData {Array} - */ -WebGLStencilManager.prototype.popStencil = function (graphics, webGLData) { - var gl = this.renderer.gl; - - this.stencilStack.pop(); - - this.count--; - - if (this.stencilStack.length === 0) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); - - } - else { - - var level = this.count; - - this.bindGraphics(graphics, webGLData, this.renderer); - - gl.colorMask(false, false, false, false); - - if (webGLData.mode === 1) { - this.reverse = !this.reverse; - - if (this.reverse) { - gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - } - else { - gl.stencilFunc(gl.EQUAL,level+1, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); - } - - // draw a quad to increment.. - gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, ( webGLData.indices.length - 4 ) * 2 ); - - gl.stencilFunc(gl.ALWAYS,0,0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INVERT); - - // draw the triangle strip! - gl.drawElements(gl.TRIANGLE_FAN, webGLData.indices.length - 4, gl.UNSIGNED_SHORT, 0 ); - - if (!this.reverse) { - gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); - } - else { - gl.stencilFunc(gl.EQUAL,level, 0xFF); - } - - } - else { - // console.log("<<>>") - if (!this.reverse) { - gl.stencilFunc(gl.EQUAL, 0xFF - (level+1), 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - } - else { - gl.stencilFunc(gl.EQUAL,level+1, 0xFF); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); - } - - gl.drawElements(gl.TRIANGLE_STRIP, webGLData.indices.length, gl.UNSIGNED_SHORT, 0 ); - - if (!this.reverse) { - gl.stencilFunc(gl.EQUAL,0xFF-(level), 0xFF); - } - else { - gl.stencilFunc(gl.EQUAL,level, 0xFF); - } - } - - gl.colorMask(true, true, true, true); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); - - - } -}; - -/** - * Destroys the mask stack. - * - */ -WebGLStencilManager.prototype.destroy = function () { - this.renderer = null; - this.stencilStack = null; -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js new file mode 100644 index 0000000..b992c31 --- /dev/null +++ b/src/core/textures/VideoBaseTexture.js @@ -0,0 +1,138 @@ +var BaseTexture = require('./BaseTexture'), + utils = require('../utils'); + +/** + * A texture of a [playing] Video. + * + * See the ["deus" demo](http://www.goodboydigital.com/pixijs/examples/deus/). + * + * @class + * @extends BaseTexture + * @namespace PIXI + * @param source {HTMLVideoElement} + * @param [scaleMode] {number} See {@link scaleModes} for possible values + */ +function VideoBaseTexture(source, scaleMode) { + if (!source){ + throw new Error('No video source element specified.'); + } + + // hook in here to check if video is already available. + // BaseTexture looks for a source.complete boolean, plus width & height. + + if ((source.readyState === source.HAVE_ENOUGH_DATA || source.readyState === source.HAVE_FUTURE_DATA) && source.width && source.height) { + source.complete = true; + } + + BaseTexture.call(this, source, scaleMode); + + this.autoUpdate = false; + + this._boundOnUpdate = this._onUpdate.bind(this); + this._boundOnCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) { + source.addEventListener('canplay', this._boundOnCanPlay); + source.addEventListener('canplaythrough', this._boundOnCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } +} + +VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); +VideoBaseTexture.prototype.constructor = VideoBaseTexture; +module.exports = VideoBaseTexture; + +VideoBaseTexture.prototype._onUpdate = function () { + if (this.autoUpdate) { + window.requestAnimationFrame(this._boundOnUpdate); + this.needsUpdate = true; + } +}; + +VideoBaseTexture.prototype._onPlayStart = function () { + if (!this.autoUpdate) { + window.requestAnimationFrame(this._boundOnUpdate); + this.autoUpdate = true; + } +}; + +VideoBaseTexture.prototype._onPlayStop = function () { + this.autoUpdate = false; +}; + +VideoBaseTexture.prototype._onCanPlay = function () { + if (event.type === 'canplaythrough') { + this.hasLoaded = true; + + + if (this.source) { + this.source.removeEventListener('canplay', this._boundOnCanPlay); + this.source.removeEventListener('canplaythrough', this._boundOnCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + // prevent multiple loaded dispatches.. + if (!this.__loaded){ + this.__loaded = true; + this.dispatchEvent({ type: 'loaded', content: this }); + } + } + } +}; + +VideoBaseTexture.prototype.destroy = function () { + if (this.source && this.source._pixiId) { + utils.BaseTextureCache[ this.source._pixiId ] = null; + delete utils.BaseTextureCache[ this.source._pixiId ]; + + this.source._pixiId = null; + delete this.source._pixiId; + } + + BaseTexture.prototype.destroy.call(this); +}; + +/** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} + * @param scaleMode {number} See {@link scaleModes} for possible values + * @return {VideoBaseTexture} + */ +VideoBaseTexture.fromVideo = function (video, scaleMode) { + if (!video._pixiId) { + video._pixiId = 'video_' + utils.uuid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; +}; + +/** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param videoSrc {string} The URL for the video. + * @param scaleMode {number} See {@link scaleModes} for possible values + * @return {VideoBaseTexture} + */ +VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) { + var video = document.createElement('video'); + + video.src = videoSrc; + video.autoPlay = true; + video.play(); + + return VideoBaseTexture.textureFromVideo(video, scaleMode); +}; diff --git a/src/primitives/Graphics.js b/src/primitives/Graphics.js deleted file mode 100644 index 7283ec5..0000000 --- a/src/primitives/Graphics.js +++ /dev/null @@ -1,1067 +0,0 @@ -var core = require('../core'), - GraphicsData = require('./GraphicsData'); - -/** - * 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() { - core.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 {object[]} - * @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 = core.CONST.blendModes.NORMAL; - - /** - * Current path - * - * @member {object} - * @private - */ - this.currentPath = null; - - /** - * Array containing some WebGL-related properties used by the WebGL renderer. - * - * @member {object[]} - * @private - */ - 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 core.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(core.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; - } - } - } -}); - -/** - * 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 core.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 core.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; - - 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 core.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 core.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 core.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 core.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 core.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 core.CanvasBuffer(bounds.width * resolution, bounds.height * resolution); - - var texture = core.Texture.fromCanvas(canvasBuffer.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - canvasBuffer.context.scale(resolution, resolution); - - canvasBuffer.context.translate(-bounds.x,-bounds.y); - - core.CanvasGraphics.renderGraphics(this, canvasBuffer.context); - - return texture; -}; - -/** - * Renders the object using the WebGL renderer - * - * @method _renderWebGL - * @param renderSession {RenderSession} - * @private - */ -Graphics.prototype._renderWebGL = function (renderSession) { - // 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 on the gpu too! - this.updateCachedSpriteTexture(); - - this.cachedSpriteDirty = false; - this.dirty = false; - } - - this._cachedSprite.worldAlpha = this.worldAlpha; - - core.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession); - - return; - } - else { - renderSession.spriteBatch.stop(); - renderSession.blendModeManager.setBlendMode(this.blendMode); - - if (this._mask) { - renderSession.maskManager.pushMask(this._mask, renderSession); - } - - if (this._filters) { - renderSession.filterManager.pushFilter(this._filterBlock); - } - - // check blend mode - if (this.blendMode !== renderSession.spriteBatch.currentBlendMode) { - renderSession.spriteBatch.currentBlendMode = this.blendMode; - - var blendModeWebGL = blendModesWebGL[renderSession.spriteBatch.currentBlendMode]; - - renderSession.spriteBatch.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]); - } - - // check if the webgl graphic needs to be updated - if (this.glDirty) { - this.dirty = true; - this.glDirty = false; - } - - core.WebGLGraphics.renderGraphics(this, renderSession); - - // only render if it has children! - if (this.children.length) { - renderSession.spriteBatch.start(); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) { - this.children[i]._renderWebGL(renderSession); - } - - renderSession.spriteBatch.stop(); - } - - if (this._filters) { - renderSession.filterManager.popFilter(); - } - - if (this._mask) { - renderSession.maskManager.popMask(this.mask, renderSession); - } - - renderSession.drawCount++; - - renderSession.spriteBatch.start(); - } -}; - -/** - * Renders the object using the Canvas renderer - * - * @method _renderCanvas - * @param renderSession {RenderSession} - * @private - */ -Graphics.prototype._renderCanvas = function (renderSession) { - // 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; - - core.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession); - - return; - } - else { - var context = renderSession.context; - var transform = this.worldTransform; - - if (this.blendMode !== renderSession.currentBlendMode) { - renderSession.currentBlendMode = this.blendMode; - context.globalCompositeOperation = blendModesCanvas[renderSession.currentBlendMode]; - } - - if (this._mask) { - renderSession.maskManager.pushMask(this._mask, renderSession); - } - - var resolution = renderSession.resolution; - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - core.CanvasGraphics.renderGraphics(this, context); - - for (var i = 0, j = this.children.length; i < j; ++i) { - this.children[i]._renderCanvas(renderSession); - } - - if (this._mask) { - renderSession.maskManager.popMask(renderSession); - } - } -}; - -/** - * 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 core.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 === Graphics.RECT || type === Graphics.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 === Graphics.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 === Graphics.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 core.CanvasBuffer(bounds.width, bounds.height); - var texture = core.Texture.fromCanvas(canvasBuffer.canvas); - - this._cachedSprite = new core.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.. - core.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 === Graphics.POLY) { - data.shape.closed = this.filling; - this.currentPath = data; - } - - this.dirty = true; - - return data; -}; - -/** - * @static - * @constant - */ -Graphics.POLY = 0; - -/** - * @static - * @constant - */ -Graphics.RECT = 1; - -/** - * @static - * @constant - */ -Graphics.CIRC = 2; - -/** - * @static - * @constant - */ -Graphics.ELIP = 3; - -/** - * @static - * @constant - */ -Graphics.RREC = 4; - -// REFACTOR: Move these to their classes, move types to central location. -core.math.Polygon.prototype.type = Graphics.POLY; -core.math.Rectangle.prototype.type = Graphics.RECT; -core.math.Circle.prototype.type = Graphics.CIRC; -core.math.Ellipse.prototype.type = Graphics.ELIP; -core.math.RoundedRectangle.prototype.type = Graphics.RREC; - diff --git a/src/primitives/GraphicsData.js b/src/primitives/GraphicsData.js deleted file mode 100644 index 9377ef7..0000000 --- a/src/primitives/GraphicsData.js +++ /dev/null @@ -1,23 +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; diff --git a/src/primitives/index.js b/src/primitives/index.js deleted file mode 100644 index 7f02649..0000000 --- a/src/primitives/index.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * @file Main export of the PIXI primitives library - * @author Mat Groves - * @copyright 2013-2015 GoodBoyDigital - * @license {@link https://github.com/GoodBoyDigital/pixi.js/blob/master/LICENSE|MIT License} - */ - -/** - * @namespace PIXI - */ -module.exports = { - Graphics: require('./Graphics'), - GraphicsData: require('./GraphicsData') -};